Fix @Where and DetachedCriteria query methods ignoring @Transactional(connection)#15418
Fix @Where and DetachedCriteria query methods ignoring @Transactional(connection)#15418jdaugherty merged 4 commits into7.0.xfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes datasource connection routing for GORM Data Service query implementations so that class/interface-level @Transactional(connection = ...) is respected when generating @Where-based and DetachedCriteria-based query methods (e.g., count(), list(), dynamic findBy*() methods).
Changes:
- Resolve the connection id from the original service method (
abstractMethodNode) instead of the generated implementation method (newMethodNode) in@WhereandDetachedCriteriaimplementers. - Reorder
DetachedCriteria.build()vswithConnection()in the@Wherepath to avoid losing the connection due tobuild()cloning. - Add unit + integration regression tests covering connection routing for
@Whereand commonDetachedCriteria-backed query methods in multi-datasource scenarios.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/services/implementers/AbstractWhereImplementer.groovy | Fix connection resolution node + ensure build() happens before withConnection() so connection isn’t dropped during cloning. |
| grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/services/implementers/AbstractDetachedCriteriaServiceImplementor.groovy | Fix connection resolution node for DetachedCriteria query implementers so class/interface @Transactional(connection) is honored. |
| grails-datamapping-core/src/test/groovy/grails/gorm/services/WhereConnectionRoutingSpec.groovy | Adds unit-level compilation/transform assertions for @Where and DetachedCriteria-implemented methods under @Transactional(connection). |
| grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/WhereQueryMultiDataSourceSpec.groovy | Adds integration coverage verifying runtime query routing against separate default + secondary H2 datasources. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
grails-datamapping-core/src/test/groovy/grails/gorm/services/WhereConnectionRoutingSpec.groovy
Outdated
Show resolved
Hide resolved
|
The suggestion is valid. The unit spec tests @where method routing but lacks a findByName test, while the integration spec covers it. Adjust the PR description to clarify that integration tests verify DetachedCriteria findByName routing. |
…connection) Data Service methods using @where annotations or DetachedCriteria-based queries (count, list, findBy*) were ignoring the class-level @transactional(connection) annotation, causing queries to execute against the default datasource instead of the specified connection. Root cause: Both AbstractWhereImplementer and AbstractDetachedCriteriaServiceImplementor called findConnectionId(newMethodNode) instead of findConnectionId(abstractMethodNode). The newMethodNode belongs to the generated $ServiceImplementation class which lacks the @transactional annotation, while abstractMethodNode belongs to the original interface/abstract class where the annotation is declared. Additionally, in AbstractWhereImplementer the build() call was placed after withConnection(), but DetachedCriteria.clone() (called internally by build) does not copy the connectionName field, causing the connection setting to be lost. Fixed by reordering build() before withConnection(). Fixes #15416 Assisted-by: Claude Code <[email protected]>
b461694 to
7ae3260
Compare
jdaugherty
left a comment
There was a problem hiding this comment.
The change is great, but what do you think about adding a test to the tck?
...re/src/test/groovy/org/grails/orm/hibernate/connections/WhereQueryMultiDataSourceSpec.groovy
Outdated
Show resolved
Hide resolved
.../main/groovy/org/grails/datastore/gorm/services/implementers/AbstractWhereImplementer.groovy
Show resolved
Hide resolved
Add TCK test (WhereQueryConnectionRoutingSpec) that validates @where queries, count, list, and findBy operations route to the correct datasource when using @transactional(connection). This ensures the AbstractWhereImplementer fix is verified across all datastore implementations, not just Hibernate. - Add WhereRoutingItem domain and WhereRoutingItemService to TCK - Extend GrailsDataTckManager with optional multi-datasource support - Implement multi-datasource in Hibernate and MongoDB TCK managers - Fix WhereQueryMultiDataSourceSpec cleanup (setup -> cleanup method) - TCK test passes on Hibernate5, MongoDB, skipped on SimpleMap Assisted-by: Claude Code <[email protected]>
...re/src/test/groovy/org/grails/orm/hibernate/connections/WhereQueryMultiDataSourceSpec.groovy
Outdated
Show resolved
Hide resolved
Replace internal GormEnhancer.findStaticApi/findInstanceApi calls with the public domain class API (Item.secondary.withNewTransaction, item.secondary.save) matching existing test conventions in MultipleDataSourcesWithEventsSpec. Assisted-by: Claude Code <[email protected]>
Summary
Fixes #15416
Data Service methods using
@Whereannotations orDetachedCriteria-based queries (count(),list(),findBy*()) were ignoring the class-level@Transactional(connection)annotation, causing queries to execute against the default datasource instead of the specified connection.Reproducer: https://github.com/jamesfredley/grails-15416-where-connection-routing
Root Cause
Two issues were found:
1. Wrong method node passed to
findConnectionId()Both
AbstractWhereImplementerandAbstractDetachedCriteriaServiceImplementorcalledfindConnectionId(newMethodNode)instead offindConnectionId(abstractMethodNode).newMethodNodebelongs to the generated$ServiceImplementationclass, which does not carry the@TransactionalannotationabstractMethodNodebelongs to the original interface/abstract class where@Transactional(connection)is declaredfindConnectionId()resolves the connection by walking up tomethodNode.getDeclaringClass()to find@Transactional- so passing the wrong node meant it could never find the annotationThis is the same class of bug fixed in #15395 for
SaveImplementer,DeleteImplementer, andFindAndDeleteImplementer.2.
build()afterwithConnection()inAbstractWhereImplementerIn the
@Wherecode path, the original order was:query = new DetachedCriteria(Foo)query = query.withConnection('secondary')- setsconnectionNamequery = query.build(closure)- internally callsclone(), which does not copyconnectionNameThe
clone()call insidebuild()creates a newDetachedCriteriainstance without the connection setting, so the connection was silently lost.Fixed by reordering to:
new DetachedCriteria->build(closure)->withConnection(name).Changes
AbstractWhereImplementer.groovyfindConnectionId(newMethodNode)->findConnectionId(abstractMethodNode)+ reorderbuild()/withConnection()AbstractDetachedCriteriaServiceImplementor.groovyfindConnectionId(newMethodNode)->findConnectionId(abstractMethodNode)Tests
Unit Tests (
WhereConnectionRoutingSpec) - 5 tests@Wheremethod generateswithConnection()call when@Transactional(connection)is present@Wheremethod does NOT generatewithConnection()when no connection specifiedDetachedCriteriacount()generateswithConnection()callDetachedCriterialist()generateswithConnection()callDetachedCriteriafindByName()generateswithConnection()callIntegration Tests (
WhereQueryMultiDataSourceSpec) - 5 testsdatasource 'ALL'@Where-annotated method returns data from secondary@Where-annotated method returns empty from default (proving isolation)count()routes to secondarylist()routes to secondaryfindByName()routes to secondaryAll existing
ServiceTransformSpectests (30) continue to pass with no regressions.