Grails 3.3.9 Не найден сеанс для текущего потока

#sql-server #hibernate #grails

#sql-server #переход в спящий режим #grails

Вопрос:

Я использую grails 3.3.9 с базой данных sql server. Я использую код, созданный по умолчанию для моего класса домена. Когда я захожу на страницу индекса, я получаю No Session found for current thread ошибку. Для использования кода, созданного по умолчанию, я немного озадачен тем, почему это происходит. В Grails 2.x после создания каркаса приложение просто работает. В любом случае, вот мой код, и, надеюсь, кто-нибудь сможет пролить свет на то, чего я, очевидно, не знаю:

application.yml

 hibernate:
    cache:
        queries: false
        use_second_level_cache: false
        use_query_cache: false
dataSource:
    pooled: true
    jmxExport: true
    driverClassName: org.h2.Driver
    username: sa
    password: ''

environments:
    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
        dataSources:
            tst:
                dbCreate: update
                url: jdbc:sqlserver://TESTSERVER;databaseName=Technician;useNTLMv2=true
                driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
                dialect: org.hibernate.dialect.SQLServer2012Dialect
                pooled: true
                username: user
                password: 'pass'
                formatSql: true
                logSql: true
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    production:
        dataSource:
            dbCreate: none
            url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
            properties:
                jmxEnabled: true
                initialSize: 5
                maxActive: 50
                minIdle: 5
                maxIdle: 25
                maxWait: 10000
                maxAge: 600000
                timeBetweenEvictionRunsMillis: 5000
                minEvictableIdleTimeMillis: 60000
                validationQuery: SELECT 1
                validationQueryTimeout: 3
                validationInterval: 15000
                testOnBorrow: true
                testWhileIdle: true
                testOnReturn: false
                jdbcInterceptors: ConnectionState
                defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED
  

build.gradle

 buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
        classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.15.1"
    }
}

version "0.1"
group "tstsupport"

apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"asset-pipeline"
apply plugin:"org.grails.grails-gsp"

repositories {
    mavenLocal()
    maven { url "https://repo.grails.org/grails/core" }
}

dependencies {
    compile "org.springframework.boot:spring-boot-starter-logging"
    compile "org.springframework.boot:spring-boot-autoconfigure"
    compile "org.grails:grails-core"
    compile "org.springframework.boot:spring-boot-starter-actuator"
    compile "org.springframework.boot:spring-boot-starter-tomcat"
    compile "org.grails:grails-web-boot"
    compile "org.grails:grails-logging"
    compile "org.grails:grails-plugin-rest"
    compile "org.grails:grails-plugin-databinding"
    compile "org.grails:grails-plugin-i18n"
    compile "org.grails:grails-plugin-services"
    compile "org.grails:grails-plugin-url-mappings"
    compile "org.grails:grails-plugin-interceptors"
    compile "org.grails.plugins:cache"
    compile "org.grails.plugins:async"
    compile "org.grails.plugins:scaffolding"
    compile "org.grails.plugins:events"
    compile "org.grails.plugins:hibernate5"
    compile "org.hibernate:hibernate-core:5.1.16.Final"
    compile "org.grails.plugins:gsp"
    console "org.grails:grails-console"
    profile "org.grails.profiles:web"
    runtime "org.glassfish.web:el-impl:2.1.2-b03"
    runtime "com.h2database:h2"
    runtime "org.apache.tomcat:tomcat-jdbc"
    runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.15.1"
    runtime "com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8"
    testCompile "org.grails:grails-gorm-testing-support"
    testCompile "org.grails.plugins:geb"
    testCompile "org.grails:grails-web-testing-support"
    testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
    testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
}

bootRun {
    jvmArgs('-Dspring.output.ansi.enabled=always')
    addResources = true
    String springProfilesActive = 'spring.profiles.active'
    systemProperty springProfilesActive, System.getProperty(springProfilesActive)
}


assets {
    minifyJs = true
    minifyCss = true
}
  

Класс домена: TST_Customer.groovy

 package TSTSupport

class TST_Technician {
    String techCode
    String techName
    boolean tec990Flag
    boolean tecCCFlag
    Date tecCreateDate
    String tecCreatedBy
    Date tecModifyDate
    String tecModifiedBy
    boolean tecActiveFlag



    static constraints = {
        techCode shared: "techCode"
        techName blank: false, maxSize: 75
        tecCreatedBy blank: false, maxSize: 35
        tecModifyDate nullable: true
        tecModifiedBy blank: false, maxSize: 35
    }

    static mapping = {
        datasource "tst"
        table name: "lkp_Technician", schema: "dbo", catalog: "Technician"
        version false

        id generator: 'assigned', name: 'techCode', type: 'string'
        techCode column: '[TechnicianCode]'
        techName column: '[TechnicianName]'
        tec990Flag column: '[Tech990Flag]'
        tecCCFlag column: '[TechCCFlag]'
        tecCreateDate column: '[TechCreateDate]'
        tecCreatedBy column: '[TechCreatedBy]'
        tecModifyDate column: '[TechModifyDate]'
        tecModifiedBy column: '[TechModifiedBy]'
        tecActiveFlag column: '[TechActiveFlag]'


    }
}
  

Контроллер

 package TSTSupport

import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*

class TST_CustomerController {

    TST_CustomerService TST_CustomerService

    static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond TST_CustomerService.list(params), model:[TST_CustomerCount: TST_CustomerService.count()]
    }

    def show(Long id) {
        respond TST_CustomerService.get(id)
    }

    def create() {
        respond new TST_Customer(params)
    }

    def save(TST_Customer TST_Customer) {
        if (TST_Customer == null) {
            notFound()
            return
        }

        try {
            TST_CustomerService.save(TST_Customer)
        } catch (ValidationException e) {
            respond TST_Customer.errors, view:'create'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.created.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
                redirect TST_Customer
            }
            '*' { respond TST_Customer, [status: CREATED] }
        }
    }

    def edit(Long id) {
        respond TST_CustomerService.get(id)
    }

    def update(TST_Customer TST_Customer) {
        if (TST_Customer == null) {
            notFound()
            return
        }

        try {
            TST_CustomerService.save(TST_Customer)
        } catch (ValidationException e) {
            respond TST_Customer.errors, view:'edit'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.updated.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
                redirect TST_Customer
            }
            '*'{ respond TST_Customer, [status: OK] }
        }
    }

    def delete(Long id) {
        if (id == null) {
            notFound()
            return
        }

        TST_CustomerService.delete(id)

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.deleted.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), id])
                redirect action:"index", method:"GET"
            }
            '*'{ render status: NO_CONTENT }
        }
    }

    protected void notFound() {
        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.not.found.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), params.id])
                redirect action: "index", method: "GET"
            }
            '*'{ render status: NOT_FOUND }
        }
    }
}
  

Обслуживание

 package TSTSupport

import grails.gorm.services.Service

@Service(TST_Customer)
interface TST_CustomerService {

    TST_Customer get(Serializable id)

    List<TST_Customer> list(Map args)

    Long count()

    void delete(Serializable id)

    TST_Customer save(TST_Customer TST_Customer)

}
  

Комментарии:

1. Я видел это в приложении, над которым я работаю, и, похоже, если вы отправляете класс домена в представление, и в этом представлении вы пытаетесь получить доступ к полю, представляющему связь, вы получите эту ошибку. В моем случае это работает, я выделил поле отношения в контроллере и передал его на карте для представления. Я не уверен, есть ли другой способ обойти.

2. @virtualdogbert У меня нет связей для этого класса домена. Фактически, это единственный класс домена в моем приложении. В этой таблице базы данных также нет внешних ключей.

3. Что ж, попробовать стоило, я бы разместил ссылку на ваш вопрос на канале Grails slack. grails. slack.com/messages/C07M0GTDE

4. @virtualdogbert Извините, но я не уверен, как получить реальное приглашение на канал. Я не вижу, где отображается контактная информация администратора.

5. grails-slack.cfapps.io

Ответ №1:

На самом деле мне пришлось искать ответ в Google (ни одно из предоставленных мне предложений не сработало для меня). В любом случае, в контроллере вы добавляете префикс прямо над classname

@Transactional("name of datasource")

Полный пример:

 package TSTSupport

import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*

@Transactional("tst")
class TST_CustomerController {

    TST_CustomerService TST_CustomerService

    static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond TST_CustomerService.list(params), model:[TST_CustomerCount: TST_CustomerService.count()]
    }

    def show(Long id) {
        respond TST_CustomerService.get(id)
    }

    def create() {
        respond new TST_Customer(params)
    }

    def save(TST_Customer TST_Customer) {
        if (TST_Customer == null) {
            notFound()
            return
        }

        try {
            TST_CustomerService.save(TST_Customer)
        } catch (ValidationException e) {
            respond TST_Customer.errors, view:'create'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.created.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
                redirect TST_Customer
            }
            '*' { respond TST_Customer, [status: CREATED] }
        }
    }

    def edit(Long id) {
        respond TST_CustomerService.get(id)
    }

    def update(TST_Customer TST_Customer) {
        if (TST_Customer == null) {
            notFound()
            return
        }

        try {
            TST_CustomerService.save(TST_Customer)
        } catch (ValidationException e) {
            respond TST_Customer.errors, view:'edit'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.updated.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
                redirect TST_Customer
            }
            '*'{ respond TST_Customer, [status: OK] }
        }
    }

    def delete(Long id) {
        if (id == null) {
            notFound()
            return
        }

        TST_CustomerService.delete(id)

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.deleted.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), id])
                redirect action:"index", method:"GET"
            }
            '*'{ render status: NO_CONTENT }
        }
    }

    protected void notFound() {
        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.not.found.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), params.id])
                redirect action: "index", method: "GET"
            }
            '*'{ render status: NOT_FOUND }
        }
    }
}
  

Комментарии:

1. Удивительно, у меня это работает в Grails 4 , спасибо