jamesfredley opened a new pull request, #15425:
URL: https://github.com/apache/grails-core/pull/15425
## Summary
Fixes #14333 and #11798 - both have the same root cause.
`GrailsOpenSessionInViewInterceptor` only registered a session for the
**default** datasource's `SessionFactory`. Calling `withSession` on a secondary
datasource during a web request threw `No Session found for current thread`
because no session was bound for that `SessionFactory` in
`TransactionSynchronizationManager`.
## Root Cause
In `HibernateDatastoreSpringInitializer` (lines 189-194), only one OSIV
interceptor is created referencing `hibernateDatastore` (the default). The
interceptor's `setHibernateDatastore()` called
`setSessionFactory(hibernateDatastore.getSessionFactory())` - binding only the
default `SessionFactory`.
**Call chain for #14333:**
1. `GormEntity.withSession` -> `AbstractHibernateGormStaticApi.withSession`
2. -> `AbstractHibernateDatastore.withSession` ->
`getHibernateTemplate().execute(callable)`
3. -> `GrailsHibernateTemplate.doExecute` -> `getSession()` ->
`sessionFactory.getCurrentSession()`
4. -> `GrailsSessionContext.currentSession()` checks
`TransactionSynchronizationManager.getResource(sessionFactory)` for the
**secondary** `SessionFactory`
5. -> Nothing bound because OSIV only registered for **default** -> `No
Session found`
**#11798** has the same root cause: domain class mapped to non-default
datasource used as command object fails validation because `validate()` needs a
session but OSIV never opened one.
## Fix
Modified `GrailsOpenSessionInViewInterceptor` to handle **multiple**
datasource `SessionFactory` instances in a single interceptor:
- `setHibernateDatastore()` now iterates all connection sources from
`HibernateDatastore` and stores non-default datasource `SessionFactory`
references in an `additionalSessionFactories` list
- `preHandle()` opens sessions and binds `SessionHolder` to
`TransactionSynchronizationManager` for each additional datasource (skipping
any that already have a session bound)
- `postHandle()` flushes additional sessions if their flush mode warrants it
- `afterCompletion()` unbinds and closes additional sessions in reverse order
This approach keeps a single interceptor bean (backward compatible) and
avoids the complexity of registering per-datasource OSIV beans.
## Tests
### Unit Tests (5 tests)
- `MultiDataSourceSessionSpec` - Tests OSIV interceptor directly:
- Default datasource session binding
- Secondary datasource session binding
- Cleanup of all datasource sessions
- Skip-if-already-bound behavior
- CRUD operations on secondary datasource
### Functional/Integration Tests (4 tests via HTTP)
- `MultiDataSourceWithSessionSpec` - Tests through actual HTTP requests to a
controller:
- `withSession` on secondary datasource does not throw (covers #14333)
- CRUD via `withSession` on secondary datasource
- Domain validation via `withSession` on secondary datasource (covers
#11798)
- `withSession` works after `executeUpdate` on secondary datasource
### Existing Tests
- All 10 `grails-data-hibernate5` tests pass
- All 42 `grails-data-hibernate5-core` connection tests pass
- All 6 existing `grails-multiple-datasources` integration tests pass
## Changed Files
| File | Change |
|------|--------|
| `GrailsOpenSessionInViewInterceptor.java` | Multi-datasource OSIV support |
| `grails-data-hibernate5/grails-plugin/build.gradle` | Added servlet-api
test dependency |
| `MultiDataSourceSessionSpec.groovy` | New unit tests |
| `SecondaryBookController.groovy` | New test controller |
| `UrlMappings.groovy` | URL mappings for test controller |
| `MultiDataSourceWithSessionSpec.groovy` | New functional tests via HTTP |
| `grails-multiple-datasources/build.gradle` | Added HTTP client + URL
mappings dependencies |
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]