-
-
Notifications
You must be signed in to change notification settings - Fork 971
Description
Description
AbstractDetachedCriteria.clone() copies 8 fields (criteria, projections, projectionList, orders, defaultMax, defaultOffset, fetchStrategies, joinTypes) but omits 4 others: connectionName, alias, lazyQuery, and associationCriteriaMap.
This causes withConnection() settings to be silently lost whenever a chained method calls clone() internally, because the cloned instance reverts connectionName to ConnectionSource.DEFAULT.
Affected Methods
Any method that calls clone() internally loses the connection setting:
where()/whereLazy()(line 812-825)max()(line 895-898)offset()(line 907-910)sort()(line 919+)
Steps to Reproduce
Reproducer app: https://github.com/jamesfredley/grails-detachedcriteria-clone-connection
git clone https://github.com/jamesfredley/grails-detachedcriteria-clone-connection.git
cd grails-detachedcriteria-clone-connection
./gradlew integrationTestMinimal code example
// withConnection('secondary') sets connectionName on the returned criteria
DetachedCriteria<Product> criteria = new DetachedCriteria<>(Product)
.withConnection('secondary') // clone() + set connectionName = 'secondary'
.where { price > 100.0 } // clone() again -- connectionName lost!
// Query runs against DEFAULT datasource instead of secondary
criteria.list()Expected Behavior
After withConnection('secondary').where { ... }, the resulting criteria should still have connectionName == 'secondary' and queries should route to the secondary datasource.
Actual Behavior
where() internally calls clone() which creates a new instance without copying connectionName. The resulting criteria has connectionName == 'DEFAULT', so queries silently route to the wrong datasource.
Root Cause
In AbstractDetachedCriteria.groovy lines 873-885:
protected AbstractDetachedCriteria<T> clone() {
AbstractDetachedCriteria criteria = newInstance()
criteria.@criteria = new ArrayList(this.criteria)
final projections = new ArrayList(this.projections)
criteria.@projections = projections
criteria.projectionList = new DetachedProjections(projections)
criteria.@orders = new ArrayList(this.orders)
criteria.defaultMax = defaultMax
criteria.defaultOffset = defaultOffset
criteria.@fetchStrategies = new HashMap<>(this.fetchStrategies)
criteria.@joinTypes = new HashMap<>(this.joinTypes)
return criteria
// Missing: connectionName, alias, lazyQuery, associationCriteriaMap
}The withConnection() method (line 865-869) works correctly in isolation - it calls clone() then sets connectionName. But any subsequent chained method that also calls clone() creates a fresh copy without the connection setting.
Suggested Fix
Add the missing field copies to clone():
protected AbstractDetachedCriteria<T> clone() {
AbstractDetachedCriteria criteria = newInstance()
criteria.@criteria = new ArrayList(this.criteria)
final projections = new ArrayList(this.projections)
criteria.@projections = projections
criteria.projectionList = new DetachedProjections(projections)
criteria.@orders = new ArrayList(this.orders)
criteria.defaultMax = defaultMax
criteria.defaultOffset = defaultOffset
criteria.@fetchStrategies = new HashMap<>(this.fetchStrategies)
criteria.@joinTypes = new HashMap<>(this.joinTypes)
criteria.connectionName = this.connectionName
criteria.alias = this.alias
criteria.lazyQuery = this.lazyQuery
criteria.@associationCriteriaMap = new HashMap<>(this.associationCriteriaMap)
return criteria
}Environment
- Grails: 7.0.7
- GORM Hibernate: via grails-data-hibernate5
- JDK: 17
Related
- Data Service query methods ignore class-level @Transactional(connection) - @Where and DetachedCriteria operations route to wrong datasource #15416 -
@Whereconnection routing fix (separate issue, same area of code) - PR Fix @Where and DetachedCriteria query methods ignoring @Transactional(connection) #15418 - Fix for Data Service query methods ignore class-level @Transactional(connection) - @Where and DetachedCriteria operations route to wrong datasource #15416
Metadata
Metadata
Assignees
Labels
Type
Projects
Status