jamesfredley commented on code in PR #16790: URL: https://github.com/apache/grails-core/pull/16790#discussion_r4408533777
########## .agents/skills/hibernate-developer/SKILL.md: ########## @@ -84,6 +87,51 @@ Keep these module-specific expectations in mind: - Utility classes in `domainbinding.util` should prefer Hibernate-aware GORM types internally, but public signatures may still need base interfaces when Spock mocks require them. - `GrailsIncrementGenerator` still contains reflection-based Hibernate 7 compatibility workarounds; avoid broad refactors unless the change explicitly addresses that area. +## Transaction and Session Lifecycle Management + +Framework code touching `GrailsHibernateTemplate`, `executeWithNewSession`, or session binding operates at the collision point of Spring's transaction synchronization, Hibernate's logical connection lifecycle, and the JDBC connection pool. Follow these non-negotiable rules: + +- **Never disconnect active write-bearing transactions**: Disconnecting an outer session in `beforeCompletion` drops uncommitted entity dirty checks and pending flushes. The outer transaction reports commit success, but rows are silently lost. Only disconnect an outer session if it is an idle or read-only `PROPAGATION_SUPPORTS` context that owns no active transaction. +- **Prevent pool starvation on non-lazy data sources**: Calling connection probes (such as `con.getAutoCommit()` or `con.getMetaData()`) on a datasource that is *not* wrapped in `LazyConnectionDataSourceProxy` immediately checks out a physical connection from the pool. If the outer session is idle and the pool is small, this starves the pool and deadlocks inner sessions. +- **Save and restore connection holders and synchronizations symmetrically**: In `executeWithNewSession`, capture the previous `ConnectionHolder` and Spring `TransactionSynchronization` list before rebinding, and strictly restore them in a `finally` block. +- **Coordinate with `SESSION_SYNCHRONIZATION_ORDER`**: When registering synchronizations with Spring's `TransactionSynchronizationManager`, ensure the logical connection disconnects before Spring's `ConnectionSynchronization` closes the physical connection, without interfering with active flushes. + +## Criteria, SQM AST, and Subquery Correlation + +Hibernate 7 compiles Criteria into an internal Semantic Query Model (SQM) AST. When working with DetachedCriteria, subqueries, and criteria builders: + +- **Lexical alias scoping**: Materialize nested aliases strictly on the association scope that declares them. Never fall back to implicit parent aliases or materialize them onto the query root. +- **Sibling association block isolation**: Sibling association blocks querying the same association name must maintain independent joins rather than collapsing into a shared join that leaks predicates between blocks. +- **Subquery correlation over inherited joins**: When correlating a subquery to an outer query through nested or inherited joins, ensure the outer join path remains accessible to the subquery predicates without dropping the comparison or creating redundant inner joins. + +## Dialect Matrix and Database Engine Differences + +Do not assume H2 or default SQL semantics apply across all supported database engines: + +- **Unique index null semantics**: + - **Single property `unique: true, nullable: true`**: A `null` value is always exempt from uniqueness checks across all datastores and in-memory test environments. Never execute a uniqueness query when the target value is `null`. + - **Oracle B-Tree index**: Oracle omits index entries where *every* column of the unique index is `null`. Multiple all-null rows are accepted by the database. If even one column in a composite unique key is non-null, Oracle indexes it and enforces uniqueness. + - **IBM Db2**: In Hibernate 7, `DB2Dialect` appends `exclude null keys` for LUW, while `DB2iDialect` and `DB2zDialect` render `create unique where not null index`. Do not force GORM `value` comparisons on all-null rows that the database dialect omits. + - **NoSQL / Neo4j**: A null unique association represents the non-existence of a relationship. Never match null unique associations against nodes that have existing relationships. +- **Shared join tables**: + - Sets receive unique keys per association, lists and maps are indexed, and bags have no key. Review Comment: Sets on a dialect that compares nulls, Oracle and Db2 LUW, share one unique key across the associations. A separate key per association rejects two valid rows that are null in one association's columns and equal in the element. Dialects that treat nulls as distinct keep a separate key per association. Lists and maps stay indexed, and bags stay without a key. -- 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]
