Transaction problem when querying across multiple datasource in the same action with Grails

Viewed 41

In my Grails 5.2 application I have a "hybrid" multi-tenant configuration, some domain are mapped to the default datasource without a multitenant implementation (these domains are for common tables, eg. configuration parameters, users, roles, etc.), other are built with multitenant implementation and are referred to different phisical databases, each one for a specific customer.

Here my "application.yml" configuration

grails:
    profile: web
    codegen:
        defaultPackage: bilce
#    spring:
#        transactionManagement:
#            proxies: false
    gorm:
        reactor:
            # Whether to translate GORM events into Reactor events
            # Disabled by default for performance reasons
            events: false
        multiTenancy:
            mode: DATABASE
            tenantResolverClass: org.grails.datastore.mapping.multitenancy.web.SessionTenantResolver
---
hibernate:
    cache:
        queries: false
        use_second_level_cache: false
        use_query_cache: false
        region:
            factory_class: 'org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory'
    hbm2ddl:
        jdbc_metadata_extraction_strategy: individually
---
dataSource:
    pooled: true
    jmxExport: true
    driverClassName: org.h2.Driver
    username: sa
    password: ''
---
environments:
    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:defaultDB;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

        dataSources:
            dataSource_customer_01:
                dbCreate: create-drop
                url: jdbc:h2:mem:customer_01_DB;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

            dataSource_customer_02:
                dbCreate: create-drop
                url: jdbc:h2:mem:customer_02_DB;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

            dataSource_customer_03:
                dbCreate: create-drop
                url: jdbc:h2:mem:customer_03_DB;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

And here my domains: "DefaultDatasourceDomain", mapped to the default datasource, without multitenant implementation

class DefaultDatasourceDomain {

    String description

    static constraints = {
    }

    static mapping = {
        datasource ConnectionSource.DEFAULT
    }

}

And "CustomerDatasourceDomain" which implements Multitenant

class CustomerDatasourceDomain implements MultiTenant<CustomerDatasourceDomain> {

    String description

    static constraints = {
    }

    static mapping = {
    }

}

Now if I try to query on both domains (the one with Multitenant is pointed to a specific datasource by the right tenant resolver) inside the same action marked with "@Transactional" I get a "no transaction is in progress" error referred to the default datasource, but if I remove the "@Transactional" annotation everything works well (even if the annotation remains present but I do the query on default datasource inside a "withTransaction" block).

This is my controller code:

class TestController {

    def index() {

        session.setAttribute(SessionTenantResolver.ATTRIBUTE, "dataSource_customer_01")
        redirect action: "doWork"

    }

    // if you mark the action with "@Transactional" annotation you get "no transaction is in progress" at the end
    // of the action execution on "DefaultDatasourceDomain", it seems no transaction are opened for default datasource
    @Transactional
    @CurrentTenant
    def doWork() {

        println("----------------------------------")
        println("DefaultDatasourceDomain contains " + DefaultDatasourceDomain.count() + " records")
        println("CustomerDatasourceDomain contains " + CustomerDatasourceDomain.count() + " records")
        println("----------------------------------")

        redirect uri: "/"

    }
}

Now in my "real world" case I have a big application developed with Grails 3.3 with hundreds of these situations working without problems, but now I'm trying to upgrade to Grails 5 and I have these problems I can't solve.

This is my stacktrace:

Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:445)
at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:3483)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1399)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1394)
at org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:113)
at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:95)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:97)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:916)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:727)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:152)
at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:91)
at it.testapp.TestController.$mt__doWork(TestController.groovy)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1268)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:1029)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:1012)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethodSafe(InvokerHelper.java:101)
at it.testapp.TestController$_doWork_closure1.doCall(TestController.groovy)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:274)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
at groovy.lang.Closure.call(Closure.java:412)
at groovy.lang.Closure.call(Closure.java:428)
at grails.gorm.multitenancy.Tenants$_withId_closure2$_closure6.doCall(Tenants.groovy:265)
at grails.gorm.multitenancy.Tenants$_withId_closure2$_closure6.call(Tenants.groovy)
at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:297)
at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:241)
at org.grails.orm.hibernate.GrailsHibernateTemplate.executeWithNewSession(GrailsHibernateTemplate.java:153)
at org.grails.orm.hibernate.GrailsHibernateTemplate.executeWithExistingOrCreateNewSession(GrailsHibernateTemplate.java:207)
at org.grails.orm.hibernate.AbstractHibernateDatastore.withNewSession(AbstractHibernateDatastore.java:369)
at grails.gorm.multitenancy.Tenants$_withId_closure2.doCall(Tenants.groovy:258)
at grails.gorm.multitenancy.Tenants$_withId_closure2.call(Tenants.groovy)
at grails.gorm.multitenancy.Tenants$CurrentTenant.withTenant(Tenants.groovy:358)
at grails.gorm.multitenancy.Tenants.withId(Tenants.groovy:236)
at org.grails.datastore.gorm.services.DefaultTenantService.withCurrent(DefaultTenantService.groovy:71)
at it.testapp.TestController.doWork(TestController.groovy)
... 55 common frames omitted
0 Answers
Related