jamesfredley commented on PR #15510:
URL: https://github.com/apache/grails-core/pull/15510#issuecomment-4087153676

   I pushed commits addressing
   
   1. several functional tests were added for hibernate 5 and not copied to the 
hibernate 7 - 
   2. we need to diff the hibernate 5 functional tests with hibernate 7 to see 
if any were subsequently changed -
   3. grails-datamapping-core changes need to be looked at closer; I might have 
merged these the wrong way. -  Implements an H5 and H7 solution for 
DetachedCriteria.count(), the merge looked right
   
   Known issues after these commits:
   
   ## H7 Test Gaps - Detailed Analysis
   ### 1. ByteBuddy Proxy Initialization (5 tests)
   **Root cause:** Hibernate 7's `ByteBuddyInterceptor.intercept()` doesn't 
distinguish Groovy's `getMetaClass()` call from a real property access. Any 
method call on a proxy - including `getId()`, truthiness checks (`if (proxy)`), 
and `isDirty()` - triggers proxy initialization via the interceptor.
   
   **Stack trace path:**
   `team.getId()` -> `ByteBuddyInterceptor.intercept()` -> 
`BasicLazyInitializer.invoke()` -> initializes proxy -> 
`Hibernate.isInitialized(team)` returns `true`
   
   **Why H5 worked (partially):** In H5, `@CompileStatic` `getId()` and 
`isDirty()` didn't trigger initialization because H5's `ByteBuddyInterceptor` 
had slightly different interception logic. The 3 dynamic Groovy tests (truthy, 
id, association id) failed in H5 too unless yakworks was present.
   
   **Why yakworks 1.1 can't fix H7:** The yakworks `ByteBuddyGroovyInterceptor` 
extends `ByteBuddyInterceptor` but its constructor takes `java.io.Serializable 
id` while H7's takes `java.lang.Object id` - binary incompatible. The library 
hasn't been updated since September 2022.
   
   **Fix path:** Write a Hibernate 7-compatible Groovy interceptor that 
overrides `intercept()` to return `null` for `getMetaClass()` and intercept 
`toString()` to avoid hydration. Roughly 50 lines of Java. Could live in 
`grails-data-hibernate7` directly rather than as an external library.
   
   **Affected tests:**
   - `getId and id property checks dont initialize proxy if in a CompileStatic 
method`
   - `getId and id dont initialize proxy`
   - `truthy check on instance should not initialize proxy`
   - `id checks on association should not initialize its proxy`
   - `isDirty should not intialize the association proxy`
   ---
   ### 2. JPA @OneToMany Unidirectional Join Table (1 test)
   **Root cause:** Hibernate 7 generates a single join table 
`EcmMaskJpa_JpaUser` for **both** `@OneToMany` associations (`createdUsers` and 
`updatedUsers`). The DDL creates columns `(EcmMaskJpa_id, createdUsers_id, 
updatedUsers_id)` where ALL columns are non-nullable. When inserting a 
`createdUsers` row, `updatedUsers_id` is NULL, violating the constraint.
   **SQL generated:**
   ```sql
   insert into EcmMaskJpa_JpaUser (EcmMaskJpa_id, createdUsers_id) values (?, ?)
   -- Fails: NULL not allowed for column "UPDATEDUSERS_ID"
   ```
   **Why:** Hibernate 7 merges the two `@OneToMany` sets into one join table by 
default when both target the same entity type. H5 created separate join tables. 
This is a Hibernate 7 behavior change for entities with multiple 
`@OneToMany(cascade=ALL)` to the same target type.
   **Fix path:** Add `@JoinTable` annotations to specify separate tables:
   ```java
   @OneToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "ecm_mask_created_users")
   Set<JpaUser> createdUsers = []
   @OneToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "ecm_mask_updated_users")
   Set<JpaUser> updatedUsers = []
   ```
   **Affected test:**
   - `test two JPA unidirectional one to many references`
   ---
   ### 3. executeQuery Named Parameters on Multi-Datasource Entity (1 test)
   **Root cause:** `Book.executeQuery("from Book where name = :name", [name: 
uniqueName])` throws `QueryParameterException: No argument for named parameter 
':name'`. The parameter map IS passed, but the H7 HQL query execution path 
doesn't bind it correctly when the entity is routed to a non-default datasource.
   **Stack trace path:**
   ```
   GormEntity.executeQuery()
     -> HibernateGormStaticApi.executeQuery()
     -> doListInternal()
     -> HibernateHqlQuery.executeQuery()
     -> SelectQueryDelegate.list()
     -> AbstractSelectionQuery.list()
     -> QueryParameterBindingsImpl.validate()
     -> throws QueryParameterException
   ```
   **Why:** The `HibernateHqlQuery` in H7 creates the Hibernate `Query` object 
and passes parameters, but when routing through a non-default datasource 
connection, the parameter binding happens on a different session than expected. 
The `SelectQueryDelegate` creates the native Hibernate query but the parameters 
from the GORM map aren't being forwarded to it properly.
   **Fix path:** Debug `HibernateHqlQuery.executeQuery()` and 
`SelectQueryDelegate.list()` to verify parameter binding when the session comes 
from a non-default datasource. Likely a session/query mismatch in the 
multi-datasource routing code.
   **Affected test:**
   - `static GORM operations use first non-default datasource for multi 
datasource entity`


-- 
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]

Reply via email to