apache / apache/grails-core

Wrong SessionFactory is used when tenant service is calling a non-tenant service. Behavior changed between 6.1.4 and 6.1.5.

Open
#14,602 0 comments 0 reactions 0 assignees View on GitHub
relates-to: gorm
Dominant language
Groovy
Stars
2.9k
Forks
975
Avg merge
1d 22h
Merged PRs (30d)
92

Description

In 6.1.4, it was possible to let a service working with multi-tenant domain call a second service working with non-multi-tenant domains.

This doesn't work since 6.1.5.
Now I can't say for sure that this was suppose to work in 6.1.4 like it does.
I see problems with having a transaction span over multiple data sources but this is a single data source with multi tenant mode SCHEMA.

I've included a test project which minimal external dependencies.
For the life of me I could not get multi tenancy working with with H2 memory database but starting H2 as network server will show the actual problem with transactions.

Running the test will work in 6.1.4.

### Task List

- [X] Steps to reproduce provided
- [X] Stacktrace (if present) provided
- [X] Example that reproduces the problem uploaded to Github
- [X] Full description of the issue provided (see below)

### Steps to Reproduce

1. Start H2 (downloaded from [http://www.h2database.com/h2-2017-06-10.zip](url))
2. Run gradlew check

### Expected Behaviour

Test should pass

### Actual Behaviour

Exception is thrown.

````
org.springframework.jdbc.BadSqlGrammarException: Hibernate operation: could not prepare statement; bad SQL grammar [insert into main_parent (version, name, id) values (?, ?, ?)]; nested exception is org.h2.jdbc.JdbcSQLException: Table "MAIN_PARENT" not found; SQL statement:
insert into main_parent (version, name, id) values (?, ?, ?) [42102-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.command.Parser.readTableOrView(Parser.java:5552)
at org.h2.command.Parser.readTableOrView(Parser.java:5529)
at org.h2.command.Parser.parseInsert(Parser.java:1062)
at org.h2.command.Parser.parsePrepared(Parser.java:417)
at org.h2.command.Parser.parse(Parser.java:321)
at org.h2.command.Parser.parse(Parser.java:293)
at org.h2.command.Parser.prepareCommand(Parser.java:258)
at org.h2.engine.Session.prepareLocal(Session.java:578)
at org.h2.server.TcpServerThread.process(TcpServerThread.java:264)
at org.h2.server.TcpServerThread.run(TcpServerThread.java:158)
at java.lang.Thread.run(Unknown Source)

at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:231)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
at org.grails.orm.hibernate.GrailsHibernateTemplate.convertJdbcAccessException(GrailsHibernateTemplate.java:731)
at org.grails.orm.hibernate.GrailsHibernateTemplate.convertHibernateAccessException(GrailsHibernateTemplate.java:719)
at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:303)
at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:243)
at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:117)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.performSave(AbstractHibernateGormInstanceApi.groovy:242)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.save(AbstractHibernateGormInstanceApi.groovy:159)
at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:151)
at se.demo.plugin.maindb.MainParentService.$tt__save(MainParentService.groovy:9)
at se.demo.plugin.maindb.MainParentService.save_closure1(MainParentService.groovy)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
at grails.gorm.transactions.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:94)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:91)
at se.demo.plugin.clientdb.TenantParentService.$tt__saveAndCallMainDbMethod(TenantParentService.groovy:22)
at se.demo.plugin.clientdb.TenantParentService._mt__saveAndCallMainDbMethod_closure4(TenantParentService.groovy)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
at grails.gorm.transactions.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:94)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:91)
at se.demo.plugin.clientdb.TenantParentService.saveAndCallMainDbMethod_closure2(TenantParentService.groovy)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
at grails.gorm.multitenancy.Tenants.withId_closure1$_closure3(Tenants.groovy:229)
at org.grails.orm.hibernate.GrailsHibernateTemplate$1.doInHibernate(GrailsHibernateTemplate.java:153)
at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:299)
at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:243)
at org.grails.orm.hibernate.GrailsHibernateTemplate.executeWithNewSession(GrailsHibernateTemplate.java:150)
at org.grails.orm.hibernate.GrailsHibernateTemplate.executeWithExistingOrCreateNewSession(GrailsHibernateTemplate.java:209)
at org.grails.orm.hibernate.AbstractHibernateDatastore.withNewSession(AbstractHibernateDatastore.java:362)
at grails.gorm.multitenancy.Tenants.withId_closure1(Tenants.groovy:222)
at grails.gorm.multitenancy.Tenants$CurrentTenant.withTenant(Tenants.groovy:322)
at grails.gorm.multitenancy.Tenants.withId(Tenants.groovy:200)
at org.grails.datastore.gorm.services.DefaultTenantService.withCurrent(DefaultTenantService.groovy:71)
at se.demo.plugin.IntegrationSpec.$tt__$spock_feature_0_0(IntegrationSpec.groovy:54)
at se.demo.plugin.IntegrationSpec.test of transactions across both main and tenant db_closure2(IntegrationSpec.groovy)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
at grails.gorm.transactions.GrailsTransactionTemplate$1.doInTransaction(GrailsTransactionTemplate.groovy:68)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at grails.gorm.transactions.GrailsTransactionTemplate.executeAndRollback(GrailsTransactionTemplate.groovy:65)
at se.demo.plugin.IntegrationSpec.test of transactions across both main and tenant db(IntegrationSpec.groovy)
Caused by: org.h2.jdbc.JdbcSQLException: Table "MAIN_PARENT" not found; SQL statement:
insert into main_parent (version, name, id) values (?, ?, ?) [42102-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.command.Parser.readTableOrView(Parser.java:5552)
at org.h2.command.Parser.readTableOrView(Parser.java:5529)
at org.h2.command.Parser.parseInsert(Parser.java:1062)
at org.h2.command.Parser.parsePrepared(Parser.java:417)
at org.h2.command.Parser.parse(Parser.java:321)
at org.h2.command.Parser.parse(Parser.java:293)
at org.h2.command.Parser.prepareCommand(Parser.java:258)
at org.h2.engine.Session.prepareLocal(Session.java:578)
at org.h2.server.TcpServerThread.process(TcpServerThread.java:264)
at org.h2.server.TcpServerThread.run(TcpServerThread.java:158)
at java.lang.Thread.run(Unknown Source)

at org.h2.engine.SessionRemote.done(SessionRemote.java:629)
at org.h2.command.CommandRemote.prepare(CommandRemote.java:83)
at org.h2.command.CommandRemote.(CommandRemote.java:49)
at org.h2.engine.SessionRemote.prepareCommand(SessionRemote.java:499)
at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1204)
at org.h2.jdbc.JdbcPreparedStatement.(JdbcPreparedStatement.java:73)
at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:288)
at org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy$LazyConnectionInvocationHandler.invoke(LazyConnectionDataSourceProxy.java:376)
at org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy$TransactionAwareInvocationHandler.invoke(TransactionAwareDataSourceProxy.java:240)
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$1.doPrepare(StatementPreparerImpl.java:87)
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:172)
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareStatement(StatementPreparerImpl.java:78)
at org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl.buildBatchStatement(AbstractBatchImpl.java:136)
at org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl.getBatchStatement(AbstractBatchImpl.java:125)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2980)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3499)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:89)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:589)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.flushSession(AbstractHibernateGormInstanceApi.groovy:280)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.performSave_closure3(AbstractHibernateGormInstanceApi.groovy:245)
at groovy.lang.Closure.call(Closure.java:414)
at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:299)
... 41 more
````

### Environment Information

- **Operating System**: Windows 10
- **GORM Version:** 6.1.7
- **Grails Version (if using Grails):** 3.3.1
- **JDK Version:** 1.8.0_111

### Example Application

[multi-tenancy-demo-plugin.zip](https://github.com/grails/grails-data-mapping/files/1401126/multi-tenancy-demo-plugin.zip)

Contributor guide

Open the contributing guide

Research direction

Start with the linked multi-tenancy-demo-plugin.zip and run gradlew check after starting the H2 network server. Read IntegrationSpec.groovy, TenantParentService.groovy, and MainParentService.groovy alongside the stack trace to trace which SessionFactory is selected across tenant and non-tenant service calls. Done means the integration test passes on the affected version and the main_parent insert uses the correct data source.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.