You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/09/09 23:41:11 UTC

(grails-core) branch 9.0.x-java-to-groovy updated (8eb4f3d846 -> 98aa2ddb56)

This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a change to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git


    from 8eb4f3d846 Convert the MongoDB entity persisters and MongoMappingContext to Groovy
     new df6135213c Convert the forked bson/json reader, scanner, writer and token classes to Groovy
     new 7a430235af Convert BsonQuery to Groovy
     new 8654cfcf1f Convert MongoQuery to Groovy
     new 9c3f86bacb Convert GormSpringDataSessionSupport to Groovy
     new 7810976a49 Convert MongoDatastore to Groovy
     new ff3dadac89 Convert the embedded MongoDB module to Groovy
     new 8cb1d488ef Convert grails-data-hibernate7-spring-orm batch 1 to Groovy
     new 932664df37 Convert grails-data-hibernate7-spring-orm batch 2 to Groovy
     new bcfb4ec514 Convert grails-data-hibernate7-spring-orm batch 3 to Groovy
     new 0c2e2c2f33 Convert grails-data-hibernate7-spring-orm batch 4 to Groovy
     new 44f4279fa0 Relocate grails-data-hibernate7-dbmigration-core Java sources into src/main/groovy
     new c585c22769 Convert grails-data-hibernate7-dbmigration-core database package to Groovy
     new de56a76d00 Convert grails-data-hibernate7-dbmigration-core connection and diff packages to Groovy
     new 98aa2ddb56 Convert grails-data-hibernate7-dbmigration-core snapshot package to Groovy

The 14 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../dbmigration-core/build.gradle                  |    4 +
 .../customfactory/CustomMetadataFactory.groovy}    |   15 +-
 .../database/HibernateClassicDatabase.groovy       |   98 ++
 .../hibernate/database/HibernateDatabase.groovy}   |  319 ++--
 .../database/HibernateEjb3Database.groovy          |  175 +++
 .../database/HibernateGenericDialect.groovy        |   17 +
 .../database/HibernateSpringBeanDatabase.groovy    |  205 +++
 .../database/HibernateSpringPackageDatabase.groovy |  176 +++
 .../database/JpaPersistenceDatabase.groovy         |   65 +
 .../database/NoOpConnectionProvider.groovy         |   74 +
 .../NoOpMultiTenantConnectionProvider.groovy       |   66 +
 .../database/connection/HibernateConnection.groovy |  358 +++++
 .../connection/HibernateConnectionMetadata.groovy  |  913 +++++++++++
 .../database/connection/HibernateDriver.groovy     |   62 +
 .../HibernateChangedColumnChangeGenerator.groovy   |  107 ++
 ...ibernateChangedForeignKeyChangeGenerator.groovy |   49 +
 ...ibernateChangedPrimaryKeyChangeGenerator.groovy |   48 +
 ...HibernateChangedSequenceChangeGenerator.groovy} |   94 +-
 ...eChangedUniqueConstraintChangeGenerator.groovy} |   41 +-
 .../HibernateMissingSequenceChangeGenerator.groovy |   40 +
 .../HibernateUnexpectedIndexChangeGenerator.groovy |   42 +
 .../HibernateCatalogSnapshotGenerator.groovy       |   41 +
 .../HibernateColumnSnapshotGenerator.groovy        |  321 ++++
 .../HibernateForeignKeySnapshotGenerator.groovy    |   85 +
 .../HibernateIndexSnapshotGenerator.groovy         |  115 ++
 .../HibernatePrimaryKeySnapshotGenerator.groovy    |   82 +
 .../HibernateSchemaSnapshotGenerator.groovy        |   44 +
 .../HibernateSequenceSnapshotGenerator.groovy      |   59 +
 .../snapshot/HibernateSnapshotGenerator.groovy     |  136 ++
 .../HibernateTableSnapshotGenerator.groovy         |   96 ++
 ...bernateUniqueConstraintSnapshotGenerator.groovy |  142 ++
 .../snapshot/HibernateViewSnapshotGenerator.groovy |   40 +
 .../extension/ExtendedSnapshotGenerator.groovy     |   12 +
 .../TableGeneratorSnapshotGenerator.groovy         |   54 +
 .../database/HibernateClassicDatabase.java         |   92 --
 .../hibernate/database/HibernateEjb3Database.java  |  156 --
 .../database/HibernateGenericDialect.java          |   13 -
 .../database/HibernateSpringBeanDatabase.java      |  184 ---
 .../database/HibernateSpringPackageDatabase.java   |  165 --
 .../hibernate/database/JpaPersistenceDatabase.java |   63 -
 .../hibernate/database/NoOpConnectionProvider.java |   70 -
 .../NoOpMultiTenantConnectionProvider.java         |   62 -
 .../database/connection/HibernateConnection.java   |  360 -----
 .../connection/HibernateConnectionMetadata.java    |  910 -----------
 .../database/connection/HibernateDriver.java       |   60 -
 .../HibernateChangedColumnChangeGenerator.java     |   92 --
 .../HibernateChangedForeignKeyChangeGenerator.java |   46 -
 .../HibernateChangedPrimaryKeyChangeGenerator.java |   45 -
 .../HibernateMissingSequenceChangeGenerator.java   |   37 -
 .../HibernateUnexpectedIndexChangeGenerator.java   |   39 -
 .../HibernateCatalogSnapshotGenerator.java         |   35 -
 .../snapshot/HibernateColumnSnapshotGenerator.java |  310 ----
 .../HibernateForeignKeySnapshotGenerator.java      |   80 -
 .../snapshot/HibernateIndexSnapshotGenerator.java  |  111 --
 .../HibernatePrimaryKeySnapshotGenerator.java      |   77 -
 .../snapshot/HibernateSchemaSnapshotGenerator.java |   39 -
 .../HibernateSequenceSnapshotGenerator.java        |   57 -
 .../snapshot/HibernateSnapshotGenerator.java       |  136 --
 .../snapshot/HibernateTableSnapshotGenerator.java  |   91 --
 ...HibernateUniqueConstraintSnapshotGenerator.java |  140 --
 .../snapshot/HibernateViewSnapshotGenerator.java   |   36 -
 .../extension/ExtendedSnapshotGenerator.java       |    8 -
 .../extension/TableGeneratorSnapshotGenerator.java |   50 -
 ...latform.java => ConfigurableJtaPlatform.groovy} |   94 +-
 ...rnateCallback.java => HibernateCallback.groovy} |   13 +-
 ...or.java => HibernateExceptionTranslator.groovy} |   55 +-
 ...xception.java => HibernateJdbcException.groovy} |   34 +-
 ...ibernateObjectRetrievalFailureException.groovy} |   39 +-
 ...eOperations.java => HibernateOperations.groovy} |  128 +-
 ...ernateOptimisticLockingFailureException.groovy} |   30 +-
 ...ception.java => HibernateQueryException.groovy} |   26 +-
 ...eption.java => HibernateSystemException.groovy} |   19 +-
 ...rnateTemplate.java => HibernateTemplate.groovy} |  856 +++++-----
 ...ger.java => HibernateTransactionManager.groovy} |  667 ++++----
 ...oryBean.java => LocalSessionFactoryBean.groovy} |  370 ++---
 ...lder.java => LocalSessionFactoryBuilder.groovy} |  383 +++--
 ...actoryUtils.java => SessionFactoryUtils.groovy} |  254 +--
 .../{SessionHolder.java => SessionHolder.groovy}   |   59 +-
 ...anContainer.java => SpringBeanContainer.groovy} |  221 ++-
 ...tion.java => SpringFlushSynchronization.groovy} |   33 +-
 ...Context.java => SpringJtaSessionContext.groovy} |   30 +-
 ...ionContext.java => SpringSessionContext.groovy} |  124 +-
 ...on.java => SpringSessionSynchronization.groovy} |  109 +-
 .../support/AsyncRequestInterceptor.groovy         |  123 ++
 .../support/AsyncRequestInterceptor.java           |  123 --
 ...or.java => OpenSessionInViewInterceptor.groovy} |  153 +-
 .../grails/datastore/bson/json/JsonReader.groovy   |  529 +++++++
 .../org/grails/datastore/bson/json/JsonReader.java |  509 ------
 .../grails/datastore/bson/json/JsonScanner.groovy  |  472 ++++++
 .../grails/datastore/bson/json/JsonScanner.java    |  532 -------
 .../grails/datastore/bson/json/JsonToken.groovy    |   78 +
 .../org/grails/datastore/bson/json/JsonToken.java  |   72 -
 .../{JsonTokenType.java => JsonTokenType.groovy}   |    9 +-
 .../grails/datastore/bson/json/JsonWriter.groovy   |  448 ++++++
 .../org/grails/datastore/bson/json/JsonWriter.java |  443 ------
 .../grails/datastore/bson/query/BsonQuery.groovy   |  931 +++++++++++
 .../org/grails/datastore/bson/query/BsonQuery.java |  901 -----------
 .../{MongoDatastore.java => MongoDatastore.groovy} | 1104 +++++++------
 .../mapping/mongo/query/MongoQuery.groovy          | 1658 ++++++++++++++++++++
 .../datastore/mapping/mongo/query/MongoQuery.java  | 1633 -------------------
 grails-data-mongodb/embedded/build.gradle          |   11 +-
 .../mongodb/embedded/EmbeddedMongoBackend.groovy}  |   11 +-
 .../embedded/EmbeddedMongoInitializer.groovy}      |  289 ++--
 .../embedded/EmbeddedMongoLifecycle.groovy}        |   35 +-
 .../mongodb/embedded/EmbeddedMongoSettings.groovy} |   86 +-
 .../embedded/FlapdoodleMongoBackend.groovy}        |  211 ++-
 .../mongodb/embedded/InMemoryMongoBackend.groovy}  |   98 +-
 .../mongodb/embedded/ReplicaSetInitiator.groovy}   |   77 +-
 .../mongodb/embedded/RunningEmbeddedMongo.groovy}  |   15 +-
 .../mongodb/GormSpringDataSessionSupport.groovy}   |   20 +-
 110 files changed, 10960 insertions(+), 10914 deletions(-)
 rename grails-data-hibernate7/dbmigration-core/src/main/{java/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java => groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.groovy} (63%)
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.groovy
 rename grails-data-hibernate7/dbmigration-core/src/main/{java/liquibase/ext/hibernate/database/HibernateDatabase.java => groovy/liquibase/ext/hibernate/database/HibernateDatabase.groovy} (55%)
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.groovy
 rename grails-data-hibernate7/dbmigration-core/src/main/{java/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java => groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.groovy} (58%)
 rename grails-data-hibernate7/dbmigration-core/src/main/{java/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java => groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.groovy} (60%)
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.groovy
 create mode 100644 grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.groovy
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateClassicDatabase.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateEjb3Database.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateGenericDialect.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/NoOpConnectionProvider.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateConnection.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateDriver.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java
 delete mode 100644 grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{ConfigurableJtaPlatform.java => ConfigurableJtaPlatform.groovy} (50%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateCallback.java => HibernateCallback.groovy} (86%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateExceptionTranslator.java => HibernateExceptionTranslator.groovy} (70%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateJdbcException.java => HibernateJdbcException.groovy} (53%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateObjectRetrievalFailureException.java => HibernateObjectRetrievalFailureException.groovy} (63%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateOperations.java => HibernateOperations.groovy} (93%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateOptimisticLockingFailureException.java => HibernateOptimisticLockingFailureException.groovy} (59%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateQueryException.java => HibernateQueryException.groovy} (65%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateSystemException.java => HibernateSystemException.groovy} (70%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateTemplate.java => HibernateTemplate.groovy} (50%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{HibernateTransactionManager.java => HibernateTransactionManager.groovy} (63%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{LocalSessionFactoryBean.java => LocalSessionFactoryBean.groovy} (67%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{LocalSessionFactoryBuilder.java => LocalSessionFactoryBuilder.groovy} (59%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{SessionFactoryUtils.java => SessionFactoryUtils.groovy} (52%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{SessionHolder.java => SessionHolder.groovy} (51%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{SpringBeanContainer.java => SpringBeanContainer.groovy} (55%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{SpringFlushSynchronization.java => SpringFlushSynchronization.groovy} (56%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{SpringJtaSessionContext.java => SpringJtaSessionContext.groovy} (63%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{SpringSessionContext.java => SpringSessionContext.groovy} (60%)
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/{SpringSessionSynchronization.java => SpringSessionSynchronization.groovy} (55%)
 create mode 100644 grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.groovy
 delete mode 100644 grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.java
 rename grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/{OpenSessionInViewInterceptor.java => OpenSessionInViewInterceptor.groovy} (70%)
 create mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.groovy
 delete mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.java
 create mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.groovy
 delete mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.java
 create mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.groovy
 delete mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.java
 rename grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/{JsonTokenType.java => JsonTokenType.groovy} (92%)
 create mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.groovy
 delete mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.java
 create mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.groovy
 delete mode 100644 grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.java
 rename grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/{MongoDatastore.java => MongoDatastore.groovy} (59%)
 create mode 100644 grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.groovy
 delete mode 100644 grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.java => groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.groovy} (90%)
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.java => groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.groovy} (65%)
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.java => groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.groovy} (78%)
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.java => groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.groovy} (60%)
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.java => groovy/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.groovy} (63%)
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.java => groovy/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.groovy} (58%)
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.java => groovy/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.groovy} (59%)
 rename grails-data-mongodb/embedded/src/main/{java/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.java => groovy/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.groovy} (92%)
 rename grails-data-mongodb/spring-data/src/main/{java/org/springframework/data/mongodb/GormSpringDataSessionSupport.java => groovy/org/springframework/data/mongodb/GormSpringDataSessionSupport.groovy} (87%)


(grails-core) 06/14: Convert the embedded MongoDB module to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit ff3dadac8950ad3257efef5eee0216d616c3e24d
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 20:19:17 2026 -0500

    Convert the embedded MongoDB module to Groovy
    
    Fifth grails-data-mongodb batch: EmbeddedMongoBackend, RunningEmbeddedMongo,
    EmbeddedMongoLifecycle, EmbeddedMongoSettings, InMemoryMongoBackend,
    ReplicaSetInitiator, FlapdoodleMongoBackend and EmbeddedMongoInitializer;
    src/main/java is removed. The build file's "written in Java" note no longer
    applied: this is a Grails Data module, so Groovy is on every consumer's
    classpath. Groovy therefore moves from compileOnly (it was only there for
    groovydoc) to a real implementation dependency, and netty-transport is
    added compileOnly: mongo-java-server's API names netty types it only
    carries at runtime, which javac never resolved but the Groovy compiler
    does when it reflects over MongoServer.
    
    Two adjustments the tests forced or the JVM requires. EmbeddedMongoSettings
    .equals() reads the other instance's fields as that.@field: property syntax
    resolved that.replicaSet to the boolean isReplicaSet() rather than the
    String field, since both accessors exist, and compared a String against
    Boolean.FALSE (EmbeddedMongoSettingsSpec caught it). EmbeddedMongoInitializer
    keeps its own copies of FlapdoodleMongoBackend's MONGOD_CLASS and NAME
    literals: javac inlined those constants so the class was never loaded when
    flapdoodle is absent, whereas Groovy compiles the reference as a field read
    that loads and links a class whose methods name flapdoodle types.
    
    Verified: compileGroovy, codenarc and validateDependencyVersions clean;
    embedded suite identical to baseline (50 tests, 0 failures); full core
    suite (which depends on this module) identical to baseline (669/0).
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 grails-data-mongodb/embedded/build.gradle          |  11 +-
 .../mongodb/embedded/EmbeddedMongoBackend.groovy}  |  11 +-
 .../embedded/EmbeddedMongoInitializer.groovy}      | 289 +++++++++++----------
 .../embedded/EmbeddedMongoLifecycle.groovy}        |  35 +--
 .../mongodb/embedded/EmbeddedMongoSettings.groovy} |  86 +++---
 .../embedded/FlapdoodleMongoBackend.groovy}        | 211 ++++++++-------
 .../mongodb/embedded/InMemoryMongoBackend.groovy}  |  98 +++----
 .../mongodb/embedded/ReplicaSetInitiator.groovy}   |  77 +++---
 .../mongodb/embedded/RunningEmbeddedMongo.groovy}  |  15 +-
 9 files changed, 428 insertions(+), 405 deletions(-)

diff --git a/grails-data-mongodb/embedded/build.gradle b/grails-data-mongodb/embedded/build.gradle
index 59a10499d7..94d0295ff6 100644
--- a/grails-data-mongodb/embedded/build.gradle
+++ b/grails-data-mongodb/embedded/build.gradle
@@ -56,10 +56,13 @@ dependencies {
         // impl: Logger, LoggerFactory
     }
 
-    // This module is written in Java so that a plain Spring Boot application can use it,
-    // but every published module still produces a groovydoc jar, and groovydoc needs
-    // Groovy on the compile classpath to infer its own.
-    compileOnly 'org.apache.groovy:groovy'
+    implementation 'org.apache.groovy:groovy'
+
+    // mongo-java-server's API names netty types (MongoServer.bind returns a netty channel's
+    // address, for instance) but only carries netty at runtime. javac never had to resolve
+    // them; the Groovy compiler reflects over the whole class to pick an overload, so netty
+    // has to be visible at compile time. Consumers still get it transitively at runtime.
+    compileOnly 'io.netty:netty-transport'
 
     // Flapdoodle runs a real mongod but pulls in jgrapht, offered under LGPL-2.1 or
     // EPL-2.0, which an Apache release should not require. Applications that want a real
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.groovy
similarity index 90%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.groovy
index 13476add06..a8fa73478b 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoBackend.groovy
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
+package org.grails.datastore.gorm.mongodb.embedded
 
 /**
  * A MongoDB server that can be started inside, or alongside, the application process.
@@ -30,14 +30,14 @@ package org.grails.datastore.gorm.mongodb.embedded;
  * @see InMemoryMongoBackend
  * @see FlapdoodleMongoBackend
  */
-public interface EmbeddedMongoBackend {
+interface EmbeddedMongoBackend {
 
     /**
      * The name this backend is selected by, through {@code embedded.mongodb.backend}.
      *
      * @return the backend name
      */
-    String getName();
+    String getName()
 
     /**
      * Whether the library this backend needs is on the classpath. Only one backend is a
@@ -46,7 +46,7 @@ public interface EmbeddedMongoBackend {
      *
      * @return true when this backend can be started
      */
-    boolean isAvailable();
+    boolean isAvailable()
 
     /**
      * Starts a server bound to localhost.
@@ -54,5 +54,6 @@ public interface EmbeddedMongoBackend {
      * @param settings the port to bind and any backend specific options
      * @return the running server, for the caller to stop
      */
-    RunningEmbeddedMongo start(EmbeddedMongoSettings settings);
+    RunningEmbeddedMongo start(EmbeddedMongoSettings settings)
+
 }
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.groovy
similarity index 65%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.groovy
index cd052a0895..1b883ec67b 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoInitializer.groovy
@@ -16,30 +16,24 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.springframework.context.ApplicationContextInitializer;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.aot.AbstractAotProcessor;
-import org.springframework.core.SpringProperties;
-import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.util.ClassUtils;
+package org.grails.datastore.gorm.mongodb.embedded
+
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.context.ApplicationContextInitializer
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.context.aot.AbstractAotProcessor
+import org.springframework.core.SpringProperties
+import org.springframework.core.env.ConfigurableEnvironment
+import org.springframework.core.env.MapPropertySource
+import org.springframework.util.ClassUtils
 
 /**
  * Starts an embedded MongoDB before the application context refreshes and publishes its
@@ -95,46 +89,58 @@ import org.springframework.util.ClassUtils;
  *
  * @since 8.0
  */
-public class EmbeddedMongoInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+@CompileStatic
+class EmbeddedMongoInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
 
     /**
      * The host that means "start one and connect me to it" rather than an address to reach.
      */
-    public static final String EMBEDDED_HOST = "embedded";
+    public static final String EMBEDDED_HOST = 'embedded'
 
-    public static final String BACKEND = "embedded.mongodb.backend";
+    public static final String BACKEND = 'embedded.mongodb.backend'
 
-    public static final String PROPERTY_NAMES = "embedded.mongodb.property-names";
+    public static final String PROPERTY_NAMES = 'embedded.mongodb.property-names'
 
-    public static final String DATABASE_DIR = "embedded.mongodb.database-dir";
+    public static final String DATABASE_DIR = 'embedded.mongodb.database-dir'
 
-    public static final String VERSION = "embedded.mongodb.version";
+    public static final String VERSION = 'embedded.mongodb.version'
 
     /**
      * The name of the replica set to run as. A transaction, a change stream and a causally
      * consistent read all need one; a standalone server refuses them. Left unset, the server is
      * standalone unless the application asks GORM for transactions.
      */
-    public static final String REPLICA_SET = "embedded.mongodb.replica-set";
+    public static final String REPLICA_SET = 'embedded.mongodb.replica-set'
 
     /**
      * What GORM is told, which is the reason an application would want a replica set at all.
      */
-    public static final String TRANSACTIONAL = "grails.mongodb.transactional";
+    public static final String TRANSACTIONAL = 'grails.mongodb.transactional'
 
-    public static final String DEFAULT_REPLICA_SET = "rs0";
+    public static final String DEFAULT_REPLICA_SET = 'rs0'
 
-    public static final String DEFAULT_PROPERTY_NAME = "grails.mongodb.url";
+    public static final String DEFAULT_PROPERTY_NAME = 'grails.mongodb.url'
 
-    private static final Logger log = LoggerFactory.getLogger(EmbeddedMongoInitializer.class);
+    private static final Logger LOG = LoggerFactory.getLogger(EmbeddedMongoInitializer)
 
-    private static final String PROPERTY_SOURCE_NAME = "embeddedMongoDB";
+    private static final String PROPERTY_SOURCE_NAME = 'embeddedMongoDB'
 
-    private static final String DEFAULT_DATABASE = "test";
+    private static final String DEFAULT_DATABASE = 'test'
 
-    private static final int DEFAULT_PORT = 27017;
+    private static final int DEFAULT_PORT = 27017
 
-    private static final int DEFAULT_SERVER_PORT = 8080;
+    private static final int DEFAULT_SERVER_PORT = 8080
+
+    /**
+     * Copies of {@code FlapdoodleMongoBackend.MONGOD_CLASS} and {@code FlapdoodleMongoBackend.NAME}.
+     * Deliberately not read from that class: Groovy compiles a reference to a constant as a field
+     * read, which loads and links the class - and its methods name flapdoodle types that are not
+     * on the classpath unless an application added them - whereas javac inlined the literals and
+     * never touched it. The presence check below only works because nothing here names the class.
+     */
+    private static final String FLAPDOODLE_MONGOD_CLASS = 'de.flapdoodle.embed.mongo.transitions.Mongod'
+
+    private static final String FLAPDOODLE_BACKEND_NAME = 'flapdoodle'
 
     /**
      * A URL asking for an embedded server, with the port and database it asks for. Credentials are
@@ -142,25 +148,25 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      * there is nothing to authenticate against.
      */
     private static final Pattern EMBEDDED_URL = Pattern.compile(
-            "^mongodb(?:\\+srv)?://(?:[^@/]*@)?" + EMBEDDED_HOST + "(?::(\\d+))?(?:/([^?]*))?(?:\\?.*)?$");
+            '^mongodb(?:\\+srv)?://(?:[^@/]*@)?' + EMBEDDED_HOST + '(?::(\\d+))?(?:/([^?]*))?(?:\\?.*)?$')
 
     /**
      * The servers this JVM started, by port. Keyed rather than a single field because two
      * application contexts in one JVM can ask for different ports, and consulted so that a
      * devtools restart reuses its own server without mistaking any other listener for one.
      */
-    private static final Map<Integer, StartedServer> STARTED = new ConcurrentHashMap<>();
+    private static final Map<Integer, StartedServer> STARTED = new ConcurrentHashMap<>()
 
-    private static final AtomicBoolean SHUTDOWN_HOOK_ADDED = new AtomicBoolean();
+    private static final AtomicBoolean SHUTDOWN_HOOK_ADDED = new AtomicBoolean()
 
     /**
      * Flapdoodle first, so that adding it to an application is all it takes to move from
      * the in-memory reimplementation to a real mongod.
      */
-    private final List<EmbeddedMongoBackend> backends;
+    private final List<EmbeddedMongoBackend> backends
 
-    public EmbeddedMongoInitializer() {
-        this(defaultBackends(EmbeddedMongoInitializer.class.getClassLoader()));
+    EmbeddedMongoInitializer() {
+        this(defaultBackends(EmbeddedMongoInitializer.getClassLoader()))
     }
 
     /**
@@ -175,27 +181,29 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      *
      * <p>So the question is asked of the class loader instead, by a name rather than by a type.</p>
      */
+    @PackageScope
     static List<EmbeddedMongoBackend> defaultBackends(ClassLoader classLoader) {
-        List<EmbeddedMongoBackend> backends = new ArrayList<>();
-        if (ClassUtils.isPresent(FlapdoodleMongoBackend.MONGOD_CLASS, classLoader)) {
-            backends.add(new FlapdoodleMongoBackend());
+        List<EmbeddedMongoBackend> backends = new ArrayList<>()
+        if (ClassUtils.isPresent(FLAPDOODLE_MONGOD_CLASS, classLoader)) {
+            backends.add(new FlapdoodleMongoBackend())
         }
-        backends.add(new InMemoryMongoBackend());
-        return backends;
+        backends.add(new InMemoryMongoBackend())
+        return backends
     }
 
+    @PackageScope
     EmbeddedMongoInitializer(List<EmbeddedMongoBackend> backends) {
-        this.backends = backends;
+        this.backends = backends
     }
 
     @Override
-    public void initialize(ConfigurableApplicationContext applicationContext) {
-        ConfigurableEnvironment environment = applicationContext.getEnvironment();
+    void initialize(ConfigurableApplicationContext applicationContext) {
+        ConfigurableEnvironment environment = applicationContext.getEnvironment()
 
-        Set<String> propertyNames = propertyNames(environment);
-        Matcher asked = firstAskingForEmbedded(environment, propertyNames);
+        Set<String> propertyNames = propertyNames(environment)
+        Matcher asked = firstAskingForEmbedded(environment, propertyNames)
         if (asked == null) {
-            return;
+            return
         }
         if (SpringProperties.getFlag(AbstractAotProcessor.AOT_PROCESSING)) {
             // Ahead-of-time processing writes bean definitions out as code. It refreshes a context
@@ -203,21 +211,21 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
             // of no use to it. Starting one is also unrecoverable: the server listens on a
             // non-daemon thread and is stopped only by a JVM shutdown hook, so generation finished
             // and then hung, holding the port, until it was killed.
-            log.debug("Not starting an embedded MongoDB: this is ahead-of-time processing");
-            return;
+            LOG.debug('Not starting an embedded MongoDB: this is ahead-of-time processing')
+            return
         }
 
-        int port = resolvePort(environment, asked.group(1));
-        String database = resolveDatabase(asked.group(2));
+        int port = resolvePort(environment, asked.group(1))
+        String database = resolveDatabase(asked.group(2))
 
-        EmbeddedMongoSettings settings = settings(environment, port);
+        EmbeddedMongoSettings settings = settings(environment, port)
         // Resolved before the reuse check rather than inside start(): which backend is being asked
         // for decides whether the running server is the one wanted, so it cannot wait until after
         // that question has been answered.
-        EmbeddedMongoBackend backend = selectBackend(environment);
+        EmbeddedMongoBackend backend = selectBackend(environment)
 
-        String url;
-        StartedServer started = discard(STARTED.get(port), settings, backend.getName());
+        String url
+        StartedServer started = discard(STARTED.get(port), settings, backend.getName())
         if (started != null) {
             // A devtools restart reuses this JVM and this class is loaded from a jar, so it
             // survives in the base classloader along with the server the previous
@@ -230,29 +238,28 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
                 // only once the context has refreshed: a datastore builds its indexes as it is
                 // constructed, which happens while beans are still being created, and a url
                 // published for a server that is not listening fails there first.
-                started.running().restart();
+                started.running().restart()
             }
-            url = "mongodb://" + started.running().getHost() + ":" + started.running().getPort() +
-                    "/" + database;
-            log.info("Reusing the embedded MongoDB this JVM already started at {}", url);
-        }
-        else {
-            url = start(backend, settings, database);
+            url = 'mongodb://' + started.running().getHost() + ':' + started.running().getPort() +
+                    '/' + database
+            LOG.info('Reusing the embedded MongoDB this JVM already started at {}', url)
+        } else {
+            url = start(backend, settings, database)
         }
 
-        Map<String, Object> published = new HashMap<>();
+        Map<String, Object> published = new HashMap<>()
         for (String propertyName : propertyNames) {
-            published.put(propertyName, url);
+            published.put(propertyName, url)
         }
-        environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, published));
+        environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, published))
 
         // Registered as a singleton rather than a bean definition because this runs before
         // any definitions are read, and the server it manages already exists by now. The
         // JVM shutdown hook above still covers the case where the context never refreshes.
-        StartedServer running = STARTED.get(port);
+        StartedServer running = STARTED.get(port)
         if (running != null) {
             applicationContext.getBeanFactory().registerSingleton(EmbeddedMongoLifecycle.BEAN_NAME,
-                    new EmbeddedMongoLifecycle(running.running()));
+                    new EmbeddedMongoLifecycle(running.running()))
         }
     }
 
@@ -262,33 +269,31 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      * enabling this was meant to replace.
      */
     private String start(EmbeddedMongoBackend backend, EmbeddedMongoSettings settings, String database) {
-        int port = settings.getPort();
+        int port = settings.getPort()
 
-        RunningEmbeddedMongo running;
+        RunningEmbeddedMongo running
         try {
-            running = backend.start(settings);
-        }
-        catch (IllegalStateException ex) {
-            throw ex;
-        }
-        catch (Exception ex) {
-            throw new IllegalStateException("Failed to start the " + backend.getName() +
-                    " embedded MongoDB on port " + port + ", which something else may already be using. " +
-                    "Name a free port as mongodb://" + EMBEDDED_HOST + ":<port>/<database>, or name a host " +
-                    "instead of " + EMBEDDED_HOST + " to use an external MongoDB.", ex);
+            running = backend.start(settings)
+        } catch (IllegalStateException ex) {
+            throw ex
+        } catch (Exception ex) {
+            throw new IllegalStateException('Failed to start the ' + backend.getName() +
+                    ' embedded MongoDB on port ' + port + ', which something else may already be using. ' +
+                    'Name a free port as mongodb://' + EMBEDDED_HOST + ':<port>/<database>, or name a host ' +
+                    'instead of ' + EMBEDDED_HOST + ' to use an external MongoDB.', ex)
         }
 
-        STARTED.put(port, new StartedServer(running, settings, backend.getName()));
-        stopEverythingAtExit();
+        STARTED.put(port, new StartedServer(running, settings, backend.getName()))
+        stopEverythingAtExit()
 
-        String url = "mongodb://" + running.getHost() + ":" + running.getPort() + "/" + database;
-        log.info("Embedded MongoDB started at {} using the {} backend", url, backend.getName());
-        return url;
+        String url = 'mongodb://' + running.getHost() + ':' + running.getPort() + '/' + database
+        LOG.info('Embedded MongoDB started at {} using the {} backend', url, backend.getName())
+        return url
     }
 
     private EmbeddedMongoSettings settings(ConfigurableEnvironment environment, int port) {
         return new EmbeddedMongoSettings(port, environment.getProperty(VERSION),
-                environment.getProperty(DATABASE_DIR), replicaSet(environment));
+                environment.getProperty(DATABASE_DIR), replicaSet(environment))
     }
 
     /**
@@ -299,16 +304,16 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      * {@code $text} query refused by the in-memory backend.
      */
     private StartedServer discard(StartedServer started, EmbeddedMongoSettings settings, String backend) {
-        if (started == null || (started.settings().equals(settings) && started.backend().equals(backend))) {
-            return started;
+        if (started == null || (started.settings() == settings && started.backend() == backend)) {
+            return started
         }
-        log.info("Replacing the embedded MongoDB this JVM started ({} on the {} backend): it is now " +
-                "asked for with {} on the {} backend", started.settings(), started.backend(), settings, backend);
-        started.running().stop();
+        LOG.info('Replacing the embedded MongoDB this JVM started ({} on the {} backend): it is now ' +
+                'asked for with {} on the {} backend', started.settings(), started.backend(), settings, backend)
+        started.running().stop()
         // Keyed removal, so a server another thread has just started on this port is not the one
         // taken out of the registry.
-        STARTED.remove(settings.getPort(), started);
-        return null;
+        STARTED.remove(settings.getPort(), started)
+        return null
     }
 
     /**
@@ -317,69 +322,69 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      * replica set itself, which wins either way.
      */
     private String replicaSet(ConfigurableEnvironment environment) {
-        String named = environment.getProperty(REPLICA_SET);
+        String named = environment.getProperty(REPLICA_SET)
         if (named != null && !named.isEmpty()) {
-            return named;
+            return named
         }
-        return environment.getProperty(TRANSACTIONAL, Boolean.class, Boolean.FALSE) ? DEFAULT_REPLICA_SET : null;
+        return environment.getProperty(TRANSACTIONAL, Boolean, Boolean.FALSE) ? DEFAULT_REPLICA_SET : null
     }
 
     private EmbeddedMongoBackend selectBackend(ConfigurableEnvironment environment) {
-        String requested = environment.getProperty(BACKEND);
+        String requested = environment.getProperty(BACKEND)
         if (requested != null && !requested.isEmpty()) {
             for (EmbeddedMongoBackend backend : this.backends) {
-                if (backend.getName().equals(requested)) {
+                if (backend.getName() == requested) {
                     if (!backend.isAvailable()) {
-                        throw new IllegalStateException(BACKEND + "=" + requested +
-                                " but its library is not on the classpath. Add it as a dependency, or choose one of " +
-                                availableNames() + ".");
+                        throw new IllegalStateException(BACKEND + '=' + requested +
+                                ' but its library is not on the classpath. Add it as a dependency, or choose one of ' +
+                                availableNames() + '.')
                     }
-                    return backend;
+                    return backend
                 }
             }
-            if (FlapdoodleMongoBackend.NAME.equals(requested)) {
-                throw new IllegalStateException(BACKEND + "=" + requested +
-                        " but its library is not on the classpath. Add it as a dependency, or choose one of " +
-                        availableNames() + ".");
+            if (FLAPDOODLE_BACKEND_NAME == requested) {
+                throw new IllegalStateException(BACKEND + '=' + requested +
+                        ' but its library is not on the classpath. Add it as a dependency, or choose one of ' +
+                        availableNames() + '.')
             }
-            throw new IllegalStateException(BACKEND + "=" + requested + " is not a known backend. Use one of " +
-                    this.backends.stream().map(EmbeddedMongoBackend::getName).collect(Collectors.joining(", ")) +
-                    ".");
+            throw new IllegalStateException(BACKEND + '=' + requested + ' is not a known backend. Use one of ' +
+                    this.backends.collect { EmbeddedMongoBackend backend -> backend.getName() }.join(', ') +
+                    '.')
         }
 
         for (EmbeddedMongoBackend backend : this.backends) {
             if (backend.isAvailable()) {
-                return backend;
+                return backend
             }
         }
-        throw new IllegalStateException("A url asked for " + EMBEDDED_HOST +
-                " but no embedded MongoDB backend is on the classpath. " +
-                "Add de.bwaldvogel:mongo-java-server for an in-memory server, or " +
-                "de.flapdoodle.embed:de.flapdoodle.embed.mongo for a real mongod.");
+        throw new IllegalStateException('A url asked for ' + EMBEDDED_HOST +
+                ' but no embedded MongoDB backend is on the classpath. ' +
+                'Add de.bwaldvogel:mongo-java-server for an in-memory server, or ' +
+                'de.flapdoodle.embed:de.flapdoodle.embed.mongo for a real mongod.')
     }
 
     private List<String> availableNames() {
-        List<String> names = new ArrayList<>();
+        List<String> names = new ArrayList<>()
         for (EmbeddedMongoBackend backend : this.backends) {
             if (backend.isAvailable()) {
-                names.add(backend.getName());
+                names.add(backend.getName())
             }
         }
-        return names;
+        return names
     }
 
     private Set<String> propertyNames(ConfigurableEnvironment environment) {
-        Set<String> propertyNames = new LinkedHashSet<>();
-        for (String propertyName : environment.getProperty(PROPERTY_NAMES, DEFAULT_PROPERTY_NAME).split(",")) {
-            String trimmed = propertyName.trim();
+        Set<String> propertyNames = new LinkedHashSet<>()
+        for (String propertyName : environment.getProperty(PROPERTY_NAMES, DEFAULT_PROPERTY_NAME).split(',')) {
+            String trimmed = propertyName.trim()
             if (!trimmed.isEmpty()) {
-                propertyNames.add(trimmed);
+                propertyNames.add(trimmed)
             }
         }
         if (propertyNames.isEmpty()) {
-            propertyNames.add(DEFAULT_PROPERTY_NAME);
+            propertyNames.add(DEFAULT_PROPERTY_NAME)
         }
-        return propertyNames;
+        return propertyNames
     }
 
     /**
@@ -391,15 +396,15 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      */
     private Matcher firstAskingForEmbedded(ConfigurableEnvironment environment, Set<String> propertyNames) {
         for (String propertyName : propertyNames) {
-            String url = environment.getProperty(propertyName);
+            String url = environment.getProperty(propertyName)
             if (url != null) {
-                Matcher matcher = EMBEDDED_URL.matcher(url.trim());
+                Matcher matcher = EMBEDDED_URL.matcher(url.trim())
                 if (matcher.matches()) {
-                    return matcher;
+                    return matcher
                 }
             }
         }
-        return null;
+        return null
     }
 
     /**
@@ -408,10 +413,10 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      */
     private int resolvePort(ConfigurableEnvironment environment, String urlPort) {
         if (urlPort != null && !urlPort.isEmpty()) {
-            return Integer.parseInt(urlPort);
+            return Integer.parseInt(urlPort)
         }
-        int serverPort = Integer.parseInt(environment.getProperty("server.port", String.valueOf(DEFAULT_SERVER_PORT)));
-        return serverPort == 0 ? DEFAULT_PORT : DEFAULT_PORT + (serverPort - DEFAULT_SERVER_PORT);
+        int serverPort = Integer.parseInt(environment.getProperty('server.port', String.valueOf(DEFAULT_SERVER_PORT)))
+        return serverPort == 0 ? DEFAULT_PORT : DEFAULT_PORT + (serverPort - DEFAULT_SERVER_PORT)
     }
 
     /**
@@ -419,7 +424,7 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      * one is written down now that the URL is what asks for a server at all.
      */
     private String resolveDatabase(String urlDatabase) {
-        return urlDatabase == null || urlDatabase.isEmpty() ? DEFAULT_DATABASE : urlDatabase;
+        return urlDatabase == null || urlDatabase.isEmpty() ? DEFAULT_DATABASE : urlDatabase
     }
 
     /**
@@ -431,11 +436,11 @@ public class EmbeddedMongoInitializer implements ApplicationContextInitializer<C
      */
     private static void stopEverythingAtExit() {
         if (SHUTDOWN_HOOK_ADDED.compareAndSet(false, true)) {
-            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            Runtime.getRuntime().addShutdownHook(new Thread({ ->
                 for (StartedServer started : STARTED.values()) {
-                    started.running().stop();
+                    started.running().stop()
                 }
-            }));
+            } as Runnable))
         }
     }
 
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.groovy
similarity index 78%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.groovy
index fad6ec27e8..9a4968a31f 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoLifecycle.groovy
@@ -16,9 +16,10 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
+package org.grails.datastore.gorm.mongodb.embedded
 
-import org.springframework.context.SmartLifecycle;
+import groovy.transform.CompileStatic
+import org.springframework.context.SmartLifecycle
 
 /**
  * Stops the embedded server while the process is checkpointed and starts it again when the
@@ -35,9 +36,10 @@ import org.springframework.context.SmartLifecycle;
  *
  * @since 8.0
  */
-public class EmbeddedMongoLifecycle implements SmartLifecycle {
+@CompileStatic
+class EmbeddedMongoLifecycle implements SmartLifecycle {
 
-    public static final String BEAN_NAME = "embeddedMongoLifecycle";
+    public static final String BEAN_NAME = 'embeddedMongoLifecycle'
 
     /**
      * Spring starts in ascending phase order and stops in descending, so a phase below
@@ -45,12 +47,12 @@ public class EmbeddedMongoLifecycle implements SmartLifecycle {
      * outlive the datastore that talks to it, which uses
      * {@code MongoDatastore.LIFECYCLE_PHASE}, itself below the web server's.
      */
-    public static final int PHASE = -2000;
+    public static final int PHASE = -2000
 
-    private final RunningEmbeddedMongo running;
+    private final RunningEmbeddedMongo running
 
-    public EmbeddedMongoLifecycle(RunningEmbeddedMongo running) {
-        this.running = running;
+    EmbeddedMongoLifecycle(RunningEmbeddedMongo running) {
+        this.running = running
     }
 
     /**
@@ -60,26 +62,27 @@ public class EmbeddedMongoLifecycle implements SmartLifecycle {
      * from a flag set when this bean was made.
      */
     @Override
-    public void start() {
+    void start() {
         if (!this.running.isRunning()) {
-            this.running.restart();
+            this.running.restart()
         }
     }
 
     @Override
-    public void stop() {
+    void stop() {
         if (this.running.isRunning()) {
-            this.running.stop();
+            this.running.stop()
         }
     }
 
     @Override
-    public boolean isRunning() {
-        return this.running.isRunning();
+    boolean isRunning() {
+        return this.running.isRunning()
     }
 
     @Override
-    public int getPhase() {
-        return PHASE;
+    int getPhase() {
+        return PHASE
     }
+
 }
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.groovy
similarity index 60%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.groovy
index d25b90e2d9..3fcef2ac54 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/EmbeddedMongoSettings.groovy
@@ -16,9 +16,9 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
+package org.grails.datastore.gorm.mongodb.embedded
 
-import java.util.Objects;
+import groovy.transform.CompileStatic
 
 /**
  * What to start a server with. Not every backend honours every setting; one that cannot
@@ -26,25 +26,26 @@ import java.util.Objects;
  *
  * @since 8.0
  */
-public final class EmbeddedMongoSettings {
+@CompileStatic
+final class EmbeddedMongoSettings {
 
-    private final int port;
+    private final int port
 
-    private final String version;
+    private final String version
 
-    private final String databaseDir;
+    private final String databaseDir
 
-    private final String replicaSet;
+    private final String replicaSet
 
-    public EmbeddedMongoSettings(int port, String version, String databaseDir) {
-        this(port, version, databaseDir, null);
+    EmbeddedMongoSettings(int port, String version, String databaseDir) {
+        this(port, version, databaseDir, null)
     }
 
-    public EmbeddedMongoSettings(int port, String version, String databaseDir, String replicaSet) {
-        this.port = port;
-        this.version = unset(version);
-        this.databaseDir = unset(databaseDir);
-        this.replicaSet = unset(replicaSet);
+    EmbeddedMongoSettings(int port, String version, String databaseDir, String replicaSet) {
+        this.port = port
+        this.version = unset(version)
+        this.databaseDir = unset(databaseDir)
+        this.replicaSet = unset(replicaSet)
     }
 
     /**
@@ -54,50 +55,50 @@ public final class EmbeddedMongoSettings {
      * decide whether the server already running is the one being asked for.
      */
     private static String unset(String value) {
-        return value == null || value.isEmpty() ? null : value;
+        return value == null || value.isEmpty() ? null : value
     }
 
     /**
      * @return the port to bind
      */
-    public int getPort() {
-        return this.port;
+    int getPort() {
+        return this.port
     }
 
     /**
      * @return the requested server version, or null for the backend default
      */
-    public String getVersion() {
-        return this.version;
+    String getVersion() {
+        return this.version
     }
 
     /**
      * @return where the data should be kept, or null to discard it when the server stops
      */
-    public String getDatabaseDir() {
-        return this.databaseDir;
+    String getDatabaseDir() {
+        return this.databaseDir
     }
 
     /**
      * @return the name of the replica set to run as, or null for a standalone server
      */
-    public String getReplicaSet() {
-        return this.replicaSet;
+    String getReplicaSet() {
+        return this.replicaSet
     }
 
     /**
      * @return whether the server is meant to be a replica set, which is what a transaction,
      *         a change stream and a causally consistent read all need
      */
-    public boolean isReplicaSet() {
-        return this.replicaSet != null && !this.replicaSet.isEmpty();
+    boolean isReplicaSet() {
+        return this.replicaSet != null && !this.replicaSet.isEmpty()
     }
 
     /**
      * @return whether the data is meant to outlive the server
      */
-    public boolean isPersistent() {
-        return this.databaseDir != null && !this.databaseDir.isEmpty();
+    boolean isPersistent() {
+        return this.databaseDir != null && !this.databaseDir.isEmpty()
     }
 
     /**
@@ -105,28 +106,31 @@ public final class EmbeddedMongoSettings {
      * application decides whether the server already running is the one it asked for.
      */
     @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
+    boolean equals(Object other) {
+        if (this.is(other)) {
+            return true
         }
         if (!(other instanceof EmbeddedMongoSettings)) {
-            return false;
+            return false
         }
-        EmbeddedMongoSettings that = (EmbeddedMongoSettings) other;
-        return this.port == that.port &&
-                Objects.equals(this.version, that.version) &&
-                Objects.equals(this.databaseDir, that.databaseDir) &&
-                Objects.equals(this.replicaSet, that.replicaSet);
+        EmbeddedMongoSettings that = (EmbeddedMongoSettings) other
+        // Direct field access: through property syntax `that.replicaSet` resolves to the boolean
+        // isReplicaSet(), not the String field, since both an is- and a get- accessor exist.
+        return this.port == that.@port &&
+                Objects.equals(this.version, that.@version) &&
+                Objects.equals(this.databaseDir, that.@databaseDir) &&
+                Objects.equals(this.replicaSet, that.@replicaSet)
     }
 
     @Override
-    public int hashCode() {
-        return Objects.hash(this.port, this.version, this.databaseDir, this.replicaSet);
+    int hashCode() {
+        return Objects.hash(this.port, this.version, this.databaseDir, this.replicaSet)
     }
 
     @Override
-    public String toString() {
-        return "port=" + this.port + ", version=" + this.version +
-                ", database-dir=" + this.databaseDir + ", replica-set=" + this.replicaSet;
+    String toString() {
+        return 'port=' + this.port + ', version=' + this.version +
+                ', database-dir=' + this.databaseDir + ', replica-set=' + this.replicaSet
     }
+
 }
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.groovy
similarity index 63%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.groovy
index fe4ec85b77..3457e17545 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/FlapdoodleMongoBackend.groovy
@@ -16,27 +16,27 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import de.flapdoodle.embed.mongo.commands.ImmutableMongodArguments;
-import de.flapdoodle.embed.mongo.commands.MongodArguments;
-import de.flapdoodle.embed.mongo.commands.ServerAddress;
-import de.flapdoodle.embed.mongo.config.Net;
-import de.flapdoodle.embed.mongo.config.Storage;
-import de.flapdoodle.embed.mongo.distribution.Version;
-import de.flapdoodle.embed.mongo.transitions.ImmutableMongod;
-import de.flapdoodle.embed.mongo.transitions.Mongod;
-import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess;
-import de.flapdoodle.embed.mongo.types.DatabaseDir;
-import de.flapdoodle.reverse.TransitionWalker;
-import de.flapdoodle.reverse.transitions.Start;
-
-import org.springframework.util.ClassUtils;
+package org.grails.datastore.gorm.mongodb.embedded
+
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+
+import de.flapdoodle.embed.mongo.commands.ImmutableMongodArguments
+import de.flapdoodle.embed.mongo.commands.MongodArguments
+import de.flapdoodle.embed.mongo.commands.ServerAddress
+import de.flapdoodle.embed.mongo.config.Net
+import de.flapdoodle.embed.mongo.config.Storage
+import de.flapdoodle.embed.mongo.distribution.Version
+import de.flapdoodle.embed.mongo.transitions.ImmutableMongod
+import de.flapdoodle.embed.mongo.transitions.Mongod
+import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess
+import de.flapdoodle.embed.mongo.types.DatabaseDir
+import de.flapdoodle.reverse.TransitionWalker
+import de.flapdoodle.reverse.transitions.Start
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.springframework.util.ClassUtils
 
 /**
  * Runs a real mongod as a child process using Flapdoodle Embedded MongoDB, which
@@ -53,64 +53,66 @@ import org.springframework.util.ClassUtils;
  *
  * @since 8.0
  */
-public class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
+@CompileStatic
+class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
 
-    public static final String NAME = "flapdoodle";
+    public static final String NAME = 'flapdoodle'
 
     /**
-     * Package-private, and a compile-time constant, so that {@link EmbeddedMongoInitializer} can ask
-     * whether flapdoodle is present without naming this class in a way that would load it.
+     * The class whose presence means flapdoodle is on the classpath. {@link EmbeddedMongoInitializer}
+     * carries its own copy of this literal rather than reading it here: Groovy compiles a reference
+     * to this field as a field read that loads and links this class, whose methods name flapdoodle
+     * types, whereas javac would have inlined the constant.
      */
-    static final String MONGOD_CLASS = "de.flapdoodle.embed.mongo.transitions.Mongod";
+    @PackageScope static final String MONGOD_CLASS = 'de.flapdoodle.embed.mongo.transitions.Mongod'
 
-    private static final String DEFAULT_VERSION = "V8_0";
+    private static final String DEFAULT_VERSION = 'V8_0'
 
     /** What runs {@code replSetInitiate}, which only a driver can send. */
-    private static final String MONGO_CLIENTS_CLASS = "com.mongodb.client.MongoClients";
+    private static final String MONGO_CLIENTS_CLASS = 'com.mongodb.client.MongoClients'
 
     /** Small, because a replica set of one keeps an oplog it never ships anywhere. */
-    private static final int OPLOG_SIZE_MB = 64;
+    private static final int OPLOG_SIZE_MB = 64
 
     @Override
-    public String getName() {
-        return NAME;
+    String getName() {
+        return NAME
     }
 
     @Override
-    public boolean isAvailable() {
-        return ClassUtils.isPresent(MONGOD_CLASS, getClass().getClassLoader());
+    boolean isAvailable() {
+        return ClassUtils.isPresent(MONGOD_CLASS, getClass().getClassLoader())
     }
 
     @Override
-    public RunningEmbeddedMongo start(EmbeddedMongoSettings settings) {
-        String versionName = settings.getVersion() != null ? settings.getVersion() : DEFAULT_VERSION;
+    RunningEmbeddedMongo start(EmbeddedMongoSettings settings) {
+        String versionName = settings.getVersion() != null ? settings.getVersion() : DEFAULT_VERSION
 
-        Version.Main version;
+        Version.Main version
         try {
-            version = Version.Main.valueOf(versionName);
-        }
-        catch (IllegalArgumentException ex) {
-            throw new IllegalStateException(EmbeddedMongoInitializer.VERSION + "=" + versionName +
-                    " is not a Version.Main constant. Use a name such as V8_0 or V7_0.", ex);
+            version = Version.Main.valueOf(versionName)
+        } catch (IllegalArgumentException ex) {
+            throw new IllegalStateException(EmbeddedMongoInitializer.VERSION + '=' + versionName +
+                    ' is not a Version.Main constant. Use a name such as V8_0 or V7_0.', ex)
         }
 
         ImmutableMongod mongod = Mongod.instance()
-                .withNet(Start.to(Net.class).initializedWith(Net.of("localhost", settings.getPort(), false)));
+                .withNet(Start.to(Net).initializedWith(Net.of('localhost', settings.getPort(), false)))
 
-        ImmutableMongodArguments arguments = MongodArguments.defaults();
+        ImmutableMongodArguments arguments = MongodArguments.defaults()
         if (settings.isPersistent()) {
-            mongod = keepDataIn(mongod, settings.getDatabaseDir());
+            mongod = keepDataIn(mongod, settings.getDatabaseDir())
             // Flapdoodle's defaults turn syncing to disc off, which anything meant to outlive the
             // process needs back on.
-            arguments = arguments.withUseDefaultSyncDelay(true);
+            arguments = arguments.withUseDefaultSyncDelay(true)
         }
         if (settings.isReplicaSet()) {
-            requireDriver(settings.getReplicaSet());
-            arguments = arguments.withReplication(Storage.of(settings.getReplicaSet(), OPLOG_SIZE_MB));
+            requireDriver(settings.getReplicaSet())
+            arguments = arguments.withReplication(Storage.of(settings.getReplicaSet(), OPLOG_SIZE_MB))
         }
-        mongod = mongod.withMongodArguments(Start.to(MongodArguments.class).initializedWith(arguments));
+        mongod = mongod.withMongodArguments(Start.to(MongodArguments).initializedWith(arguments))
 
-        return new RunningMongod(this, mongod, version, settings.getReplicaSet());
+        return new RunningMongod(this, mongod, version, settings.getReplicaSet())
     }
 
     /**
@@ -119,8 +121,9 @@ public class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
      * <p>Not private, so that the specification can stand in a failure here and see that the server
      * this started does not outlive it.
      */
+    @PackageScope
     void initiateReplicaSet(String host, int port, String replicaSet) {
-        ReplicaSetInitiator.initiate(host, port, replicaSet);
+        ReplicaSetInitiator.initiate(host, port, replicaSet)
     }
 
     /**
@@ -129,9 +132,9 @@ public class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
      */
     private void requireDriver(String replicaSet) {
         if (!ClassUtils.isPresent(MONGO_CLIENTS_CLASS, getClass().getClassLoader())) {
-            throw new IllegalStateException(EmbeddedMongoInitializer.REPLICA_SET + "=" + replicaSet +
-                    " needs the MongoDB driver on the class path to initiate the replica set. Add " +
-                    "org.mongodb:mongodb-driver-sync, or leave the server standalone.");
+            throw new IllegalStateException(EmbeddedMongoInitializer.REPLICA_SET + '=' + replicaSet +
+                    ' needs the MongoDB driver on the class path to initiate the replica set. Add ' +
+                    'org.mongodb:mongodb-driver-sync, or leave the server standalone.')
         }
     }
 
@@ -140,56 +143,53 @@ public class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
      * has to change before anything written here outlives the process.
      */
     private ImmutableMongod keepDataIn(ImmutableMongod mongod, String databaseDir) {
-        Path path;
+        Path path
         try {
-            path = Files.createDirectories(Paths.get(databaseDir).toAbsolutePath());
-        }
-        catch (IOException ex) {
-            throw new IllegalStateException("Could not create the embedded MongoDB directory " + databaseDir, ex);
+            path = Files.createDirectories(Paths.get(databaseDir).toAbsolutePath())
+        } catch (IOException ex) {
+            throw new IllegalStateException('Could not create the embedded MongoDB directory ' + databaseDir, ex)
         }
-        return mongod.withDatabaseDir(Start.to(DatabaseDir.class).initializedWith(DatabaseDir.of(path)));
+        return mongod.withDatabaseDir(Start.to(DatabaseDir).initializedWith(DatabaseDir.of(path)))
     }
 
     private static final class RunningMongod implements RunningEmbeddedMongo {
 
         /** Held so {@link #restart()} can start the same mongod again after a CRaC restore. */
-        private final ImmutableMongod mongod;
+        private final ImmutableMongod mongod
 
-        private final FlapdoodleMongoBackend backend;
+        private final FlapdoodleMongoBackend backend
 
-        private final Version.Main version;
+        private final Version.Main version
 
-        private final String replicaSet;
+        private final String replicaSet
 
-        private volatile TransitionWalker.ReachedState<RunningMongodProcess> running;
+        private volatile TransitionWalker.ReachedState<RunningMongodProcess> running
 
-        private final ServerAddress address;
+        private final ServerAddress address
 
         private RunningMongod(FlapdoodleMongoBackend backend, ImmutableMongod mongod,
-                Version.Main version, String replicaSet) {
-            this.backend = backend;
-            this.mongod = mongod;
-            this.version = version;
-            this.replicaSet = replicaSet;
-            this.running = mongod.start(version);
-            this.address = this.running.current().getServerAddress();
+                              Version.Main version, String replicaSet) {
+            this.backend = backend
+            this.mongod = mongod
+            this.version = version
+            this.replicaSet = replicaSet
+            this.running = mongod.start(version)
+            this.address = this.running.current().getServerAddress()
             try {
-                initiateReplicaSet();
-            }
-            catch (RuntimeException | Error setDidNotForm) {
+                initiateReplicaSet()
+            } catch (RuntimeException | Error setDidNotForm) {
                 // The process is running and this object is about to be thrown away, so nothing
                 // would ever stop it: mongod is a child process that outlives the JVM that started
                 // it, and it holds both the port and the database directory. The next start would
                 // find them taken by a server no one can name.
                 try {
-                    stop();
-                }
-                catch (RuntimeException | Error alsoFailedToStop) {
+                    stop()
+                } catch (RuntimeException | Error alsoFailedToStop) {
                     // The reason the caller asked is the set that never formed; how the cleanup
                     // then went is worth carrying, but not worth reporting in its place.
-                    setDidNotForm.addSuppressed(alsoFailedToStop);
+                    setDidNotForm.addSuppressed(alsoFailedToStop)
                 }
-                throw setDidNotForm;
+                throw setDidNotForm
             }
         }
 
@@ -201,19 +201,19 @@ public class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
          */
         private void initiateReplicaSet() {
             if (this.replicaSet == null || this.replicaSet.isEmpty()) {
-                return;
+                return
             }
-            this.backend.initiateReplicaSet(getHost(), getPort(), this.replicaSet);
+            this.backend.initiateReplicaSet(getHost(), getPort(), this.replicaSet)
         }
 
         @Override
-        public String getHost() {
-            return this.address.getHost();
+        String getHost() {
+            return this.address.getHost()
         }
 
         @Override
-        public int getPort() {
-            return this.address.getPort();
+        int getPort() {
+            return this.address.getPort()
         }
 
         /**
@@ -223,25 +223,24 @@ public class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
          * it tears the server down, so a second teardown fails on the files the first one removed.
          */
         @Override
-        public synchronized void stop() {
-            TransitionWalker.ReachedState<RunningMongodProcess> current = this.running;
+        synchronized void stop() {
+            TransitionWalker.ReachedState<RunningMongodProcess> current = this.running
             if (current == null) {
-                return;
+                return
             }
             try {
-                current.close();
-            }
-            finally {
+                current.close()
+            } finally {
                 // Cleared only once the server is actually down. Clearing first meant a close that
                 // threw left a running mongod that this object reported as stopped, which is a
                 // process nothing would stop and a port a restart would try to bind again.
-                this.running = null;
+                this.running = null
             }
         }
 
         @Override
-        public boolean isRunning() {
-            return this.running != null;
+        boolean isRunning() {
+            return this.running != null
         }
 
         /**
@@ -255,23 +254,23 @@ public class FlapdoodleMongoBackend implements EmbeddedMongoBackend {
          * lifecycle bean stops the server before the checkpoint that this restores from.
          */
         @Override
-        public synchronized void restart() {
-            stop();
-            this.running = this.mongod.start(this.version);
+        synchronized void restart() {
+            stop()
+            this.running = this.mongod.start(this.version)
             try {
-                initiateReplicaSet();
-            }
-            catch (RuntimeException | Error setDidNotForm) {
+                initiateReplicaSet()
+            } catch (RuntimeException | Error setDidNotForm) {
                 // As in the constructor: a process that is running and cannot be used is worse than
                 // no process, and this one would hold the port against every later attempt.
                 try {
-                    stop();
-                }
-                catch (RuntimeException | Error alsoFailedToStop) {
-                    setDidNotForm.addSuppressed(alsoFailedToStop);
+                    stop()
+                } catch (RuntimeException | Error alsoFailedToStop) {
+                    setDidNotForm.addSuppressed(alsoFailedToStop)
                 }
-                throw setDidNotForm;
+                throw setDidNotForm
             }
         }
+
     }
+
 }
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.groovy
similarity index 58%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.groovy
index 6fe46b29fb..00d85146f9 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/InMemoryMongoBackend.groovy
@@ -16,12 +16,12 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
+package org.grails.datastore.gorm.mongodb.embedded
 
-import de.bwaldvogel.mongo.MongoServer;
-import de.bwaldvogel.mongo.backend.memory.MemoryBackend;
-
-import org.springframework.util.ClassUtils;
+import de.bwaldvogel.mongo.MongoServer
+import de.bwaldvogel.mongo.backend.memory.MemoryBackend
+import groovy.transform.CompileStatic
+import org.springframework.util.ClassUtils
 
 /**
  * Runs MongoDB inside this JVM using mongo-java-server, which reimplements the MongoDB
@@ -35,42 +35,43 @@ import org.springframework.util.ClassUtils;
  *
  * @since 8.0
  */
-public class InMemoryMongoBackend implements EmbeddedMongoBackend {
+@CompileStatic
+class InMemoryMongoBackend implements EmbeddedMongoBackend {
 
-    public static final String NAME = "in-memory";
+    public static final String NAME = 'in-memory'
 
-    private static final String SERVER_CLASS = "de.bwaldvogel.mongo.MongoServer";
+    private static final String SERVER_CLASS = 'de.bwaldvogel.mongo.MongoServer'
 
     @Override
-    public String getName() {
-        return NAME;
+    String getName() {
+        return NAME
     }
 
     @Override
-    public boolean isAvailable() {
-        return ClassUtils.isPresent(SERVER_CLASS, getClass().getClassLoader());
+    boolean isAvailable() {
+        return ClassUtils.isPresent(SERVER_CLASS, getClass().getClassLoader())
     }
 
     @Override
-    public RunningEmbeddedMongo start(EmbeddedMongoSettings settings) {
+    RunningEmbeddedMongo start(EmbeddedMongoSettings settings) {
         if (settings.isPersistent()) {
-            throw new IllegalStateException("The " + NAME + " backend keeps everything in memory and cannot honour " +
-                    EmbeddedMongoInitializer.DATABASE_DIR + ". Add " +
-                    "de.flapdoodle.embed:de.flapdoodle.embed.mongo to run a real mongod that can, or remove the " +
-                    "directory to accept a database that is discarded when the server stops.");
+            throw new IllegalStateException('The ' + NAME + ' backend keeps everything in memory and cannot honour ' +
+                    EmbeddedMongoInitializer.DATABASE_DIR + '. Add ' +
+                    'de.flapdoodle.embed:de.flapdoodle.embed.mongo to run a real mongod that can, or remove the ' +
+                    'directory to accept a database that is discarded when the server stops.')
         }
 
         if (settings.isReplicaSet()) {
-            throw new IllegalStateException("The " + NAME + " backend reimplements the wire protocol and cannot be a " +
-                    "replica set, which is what a transaction, a change stream and a causally consistent read need. " +
-                    "Add de.flapdoodle.embed:de.flapdoodle.embed.mongo to run a real mongod that can, or unset " +
-                    EmbeddedMongoInitializer.REPLICA_SET + " and " + EmbeddedMongoInitializer.TRANSACTIONAL + ".");
+            throw new IllegalStateException('The ' + NAME + ' backend reimplements the wire protocol and cannot be a ' +
+                    'replica set, which is what a transaction, a change stream and a causally consistent read need. ' +
+                    'Add de.flapdoodle.embed:de.flapdoodle.embed.mongo to run a real mongod that can, or unset ' +
+                    EmbeddedMongoInitializer.REPLICA_SET + ' and ' + EmbeddedMongoInitializer.TRANSACTIONAL + '.')
         }
 
-        MemoryBackend backend = new RetainingMemoryBackend();
-        MongoServer server = new MongoServer(backend);
-        server.bind("localhost", settings.getPort());
-        return new RunningInMemoryMongo(backend, server);
+        MemoryBackend backend = new RetainingMemoryBackend()
+        MongoServer server = new MongoServer(backend)
+        server.bind('localhost', settings.getPort())
+        return new RunningInMemoryMongo(backend, server)
     }
 
     /**
@@ -88,9 +89,10 @@ public class InMemoryMongoBackend implements EmbeddedMongoBackend {
     private static final class RetainingMemoryBackend extends MemoryBackend {
 
         @Override
-        public void close() {
+        void close() {
             // Deliberately empty; see the class comment.
         }
+
     }
 
     private static final class RunningInMemoryMongo implements RunningEmbeddedMongo {
@@ -100,51 +102,53 @@ public class InMemoryMongoBackend implements EmbeddedMongoBackend {
          * there. The data lives on the heap, so a CRaC checkpoint image preserves it and a
          * restored process comes back with the collections it had.
          */
-        private final MemoryBackend backend;
+        private final MemoryBackend backend
 
-        private volatile MongoServer server;
+        private volatile MongoServer server
 
-        private volatile boolean running = true;
+        private volatile boolean running = true
 
         /**
          * The port that was actually bound, which is not the requested one when that was 0.
          * Restarting reuses it so the url published into the environment stays correct.
          */
-        private final int port;
+        private final int port
 
         private RunningInMemoryMongo(MemoryBackend backend, MongoServer server) {
-            this.backend = backend;
-            this.server = server;
-            this.port = server.getLocalAddress().getPort();
+            this.backend = backend
+            this.server = server
+            this.port = server.getLocalAddress().getPort()
         }
 
         @Override
-        public String getHost() {
-            return "localhost";
+        String getHost() {
+            return 'localhost'
         }
 
         @Override
-        public int getPort() {
-            return this.port;
+        int getPort() {
+            return this.port
         }
 
         @Override
-        public void stop() {
-            this.server.shutdownNow();
-            this.running = false;
+        void stop() {
+            this.server.shutdownNow()
+            this.running = false
         }
 
         @Override
-        public boolean isRunning() {
-            return this.running;
+        boolean isRunning() {
+            return this.running
         }
 
         @Override
-        public void restart() {
-            MongoServer restarted = new MongoServer(this.backend);
-            restarted.bind("localhost", this.port);
-            this.server = restarted;
-            this.running = true;
+        void restart() {
+            MongoServer restarted = new MongoServer(this.backend)
+            restarted.bind('localhost', this.port)
+            this.server = restarted
+            this.running = true
         }
+
     }
+
 }
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.groovy
similarity index 59%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.groovy
index acd24c9d2b..2ed50aefc0 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/ReplicaSetInitiator.groovy
@@ -16,16 +16,16 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
+package org.grails.datastore.gorm.mongodb.embedded
 
-import java.time.Duration;
-import java.util.Collections;
-import java.util.List;
+import java.time.Duration
 
-import com.mongodb.MongoCommandException;
-import com.mongodb.client.MongoClient;
-import com.mongodb.client.MongoClients;
-import org.bson.Document;
+import com.mongodb.MongoCommandException
+import com.mongodb.client.MongoClient
+import com.mongodb.client.MongoClients
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.bson.Document
 
 /**
  * Turns a mongod started with a replica set name into one that answers.
@@ -41,38 +41,43 @@ import org.bson.Document;
  *
  * @since 8.0
  */
+@CompileStatic
+@PackageScope
 final class ReplicaSetInitiator {
 
     /** {@code AlreadyInitialized}: the set this is being asked to create is the set it found. */
-    private static final int ALREADY_INITIALIZED = 23;
+    private static final int ALREADY_INITIALIZED = 23
 
-    private static final Duration TIMEOUT = Duration.ofSeconds(30);
+    private static final Duration TIMEOUT = Duration.ofSeconds(30)
 
-    private static final long POLL_MILLIS = 100;
+    private static final long POLL_MILLIS = 100
 
     private ReplicaSetInitiator() {
     }
 
+    @PackageScope
     static void initiate(String host, int port, String replicaSet) {
-        String address = host + ":" + port;
+        String address = host + ':' + port
         // Direct, because the set does not exist yet: a driver discovering a replica set would have
         // nothing to discover and would wait for a primary that cannot be elected until this runs.
-        try (MongoClient client = MongoClients.create("mongodb://" + address + "/?directConnection=true")) {
-            initiate(client, address, replicaSet);
-            awaitPrimary(client, address, replicaSet);
+        MongoClient client = MongoClients.create('mongodb://' + address + '/?directConnection=true')
+        try {
+            initiate(client, address, replicaSet)
+            awaitPrimary(client, address, replicaSet)
+        } finally {
+            client.close()
         }
     }
 
     private static void initiate(MongoClient client, String address, String replicaSet) {
-        List<Document> members = Collections.singletonList(new Document("_id", 0).append("host", address));
-        Document configuration = new Document("_id", replicaSet).append("members", members);
+        List<Document> members = Collections.singletonList(new Document('_id', 0).append('host', address))
+        Document configuration = new Document('_id', replicaSet).append('members', members)
         try {
-            client.getDatabase("admin").runCommand(new Document("replSetInitiate", configuration));
-        }
-        catch (MongoCommandException ex) {
+            client.getDatabase('admin').runCommand(new Document('replSetInitiate', configuration))
+        } catch (MongoCommandException ex) {
             if (ex.getErrorCode() != ALREADY_INITIALIZED) {
-                throw new IllegalStateException("Could not initiate the replica set " + replicaSet +
-                        " on the embedded MongoDB at " + address, ex);
+                throw new IllegalStateException('Could not initiate the replica set ' + replicaSet +
+                        ' on the embedded MongoDB at ' + address, ex)
             }
         }
     }
@@ -83,32 +88,30 @@ final class ReplicaSetInitiator {
      * failing against a server that was about to be ready.
      */
     private static void awaitPrimary(MongoClient client, String address, String replicaSet) {
-        long deadline = System.nanoTime() + TIMEOUT.toNanos();
+        long deadline = System.nanoTime() + TIMEOUT.toNanos()
         while (System.nanoTime() < deadline) {
             if (isPrimary(client)) {
-                return;
+                return
             }
             try {
-                Thread.sleep(POLL_MILLIS);
-            }
-            catch (InterruptedException ex) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted while waiting for the embedded MongoDB at " +
-                        address + " to elect itself primary of " + replicaSet, ex);
+                Thread.sleep(POLL_MILLIS)
+            } catch (InterruptedException ex) {
+                Thread.currentThread().interrupt()
+                throw new IllegalStateException('Interrupted while waiting for the embedded MongoDB at ' +
+                        address + ' to elect itself primary of ' + replicaSet, ex)
             }
         }
-        throw new IllegalStateException("The embedded MongoDB at " + address + " did not become primary of " +
-                replicaSet + " within " + TIMEOUT.toSeconds() + " seconds.");
+        throw new IllegalStateException('The embedded MongoDB at ' + address + ' did not become primary of ' +
+                replicaSet + ' within ' + TIMEOUT.toSeconds() + ' seconds.')
     }
 
     private static boolean isPrimary(MongoClient client) {
         try {
-            Document hello = client.getDatabase("admin").runCommand(new Document("hello", 1));
-            return Boolean.TRUE.equals(hello.getBoolean("isWritablePrimary"));
-        }
-        catch (RuntimeException ex) {
+            Document hello = client.getDatabase('admin').runCommand(new Document('hello', 1))
+            return Boolean.TRUE.equals(hello.getBoolean('isWritablePrimary'))
+        } catch (RuntimeException ex) {
             // The server closes connections as it transitions, so a failure here means "not yet".
-            return false;
+            return false
         }
     }
 
diff --git a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.java b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.groovy
similarity index 92%
rename from grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.java
rename to grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.groovy
index a5516952e0..2b50fea231 100644
--- a/grails-data-mongodb/embedded/src/main/java/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.java
+++ b/grails-data-mongodb/embedded/src/main/groovy/org/grails/datastore/gorm/mongodb/embedded/RunningEmbeddedMongo.groovy
@@ -16,37 +16,37 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.grails.datastore.gorm.mongodb.embedded;
+package org.grails.datastore.gorm.mongodb.embedded
 
 /**
  * A started embedded MongoDB server.
  *
  * @since 8.0
  */
-public interface RunningEmbeddedMongo {
+interface RunningEmbeddedMongo {
 
     /**
      * @return the host the server is listening on
      */
-    String getHost();
+    String getHost()
 
     /**
      * @return the port the server is listening on, which is the port that was actually
      * bound rather than the one that was requested
      */
-    int getPort();
+    int getPort()
 
     /**
      * Stops the server. Called from a JVM shutdown hook, so it must not throw.
      */
-    void stop();
+    void stop()
 
     /**
      * @return whether the server is listening, which it is until {@link #stop()} and again
      *         after {@link #restart()}. A server outlives the application context that started
      *         it, so a later context has to ask rather than assume.
      */
-    boolean isRunning();
+    boolean isRunning()
 
     /**
      * Binds the server again on the port it was already using, after {@link #stop()}.
@@ -61,5 +61,6 @@ public interface RunningEmbeddedMongo {
      * onto the same backend. Flapdoodle keeps whatever is in its {@code database-dir} and
      * loses the rest, since the {@code mongod} process is not part of the image.
      */
-    void restart();
+    void restart()
+
 }


(grails-core) 05/14: Convert MongoDatastore to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 7810976a490cebe027fe8506cc5dbc250ca46db0
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 20:02:55 2026 -0500

    Convert MongoDatastore to Groovy
    
    Seventh grails-data-mongodb batch: MongoDatastore, the last Java file in
    the core module, which now contains no .java sources.
    
    Adjustments: createChildDatastore is @PackageScope because the anonymous
    ConnectionSourcesListener registered in the constructor calls it; the
    inner MongoGormEnhancer's getDatastoreForQualifier uses a local instead
    of reassigning its parameter; the field reads that went through
    this.mappingContext use getMappingContext(); the mapToPropertyResolver(null)
    calls cast the null to Map; the two intentionally empty catch blocks in
    close() name their exception "ignored"; and PersistentEntityCodeRegistry
    casts its PersistentEntityCodec to Codec<T> on return.
    
    Verified: compileGroovy, codenarc and checkstyle clean; full core suite
    identical to baseline (669 tests, 0 failures, 45 skipped).
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 .../{MongoDatastore.java => MongoDatastore.groovy} | 1104 ++++++++++----------
 1 file changed, 542 insertions(+), 562 deletions(-)

diff --git a/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java b/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.groovy
similarity index 59%
rename from grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java
rename to grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.groovy
index 1be229be4e..432160696e 100644
--- a/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java
+++ b/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.groovy
@@ -13,101 +13,90 @@
  * limitations under the License.
  */
 
-package org.grails.datastore.mapping.mongo;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-
-import groovy.lang.Closure;
-
-import jakarta.annotation.PreDestroy;
-import jakarta.persistence.FlushModeType;
-
-import com.mongodb.MongoClientSettings;
-import com.mongodb.MongoCommandException;
-import com.mongodb.client.MongoClient;
-import com.mongodb.client.MongoIterable;
-import com.mongodb.client.model.IndexOptions;
-import com.mongodb.connection.ClusterType;
-import org.bson.Document;
-import org.bson.codecs.Codec;
-import org.bson.codecs.configuration.CodecProvider;
-import org.bson.codecs.configuration.CodecRegistries;
-import org.bson.codecs.configuration.CodecRegistry;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.MessageSource;
-import org.springframework.context.SmartLifecycle;
-import org.springframework.context.support.StaticMessageSource;
-import org.springframework.core.env.PropertyResolver;
-import org.springframework.transaction.PlatformTransactionManager;
-
-import grails.gorm.multitenancy.Tenants;
-import grails.util.GrailsMessageSourceUtils;
-import org.grails.datastore.bson.codecs.CodecExtensions;
-import org.grails.datastore.gorm.GormEnhancer;
-import org.grails.datastore.gorm.GormInstanceApi;
-import org.grails.datastore.gorm.GormValidationApi;
-import org.grails.datastore.gorm.events.AutoTimestampEventListener;
-import org.grails.datastore.gorm.events.ConfigurableApplicationEventPublisher;
-import org.grails.datastore.gorm.events.DefaultApplicationEventPublisher;
-import org.grails.datastore.gorm.events.DomainEventListener;
-import org.grails.datastore.gorm.mongo.MongoGormEnhancer;
-import org.grails.datastore.gorm.mongo.api.MongoStaticApi;
-import org.grails.datastore.gorm.multitenancy.MultiTenantEventListener;
-import org.grails.datastore.gorm.utils.ClasspathEntityScanner;
-import org.grails.datastore.gorm.validation.constraints.MappingContextAwareConstraintFactory;
-import org.grails.datastore.gorm.validation.constraints.builtin.UniqueConstraint;
-import org.grails.datastore.gorm.validation.constraints.registry.ConstraintRegistry;
-import org.grails.datastore.gorm.validation.listener.ValidationEventListener;
-import org.grails.datastore.gorm.validation.registry.support.ValidatorRegistries;
-import org.grails.datastore.mapping.config.Settings;
-import org.grails.datastore.mapping.core.AbstractDatastore;
-import org.grails.datastore.mapping.core.Datastore;
-import org.grails.datastore.mapping.core.DatastoreUtils;
-import org.grails.datastore.mapping.core.Session;
-import org.grails.datastore.mapping.core.StatelessDatastore;
-import org.grails.datastore.mapping.core.connections.ConnectionSource;
-import org.grails.datastore.mapping.core.connections.ConnectionSources;
-import org.grails.datastore.mapping.core.connections.ConnectionSourcesInitializer;
-import org.grails.datastore.mapping.core.connections.ConnectionSourcesListener;
-import org.grails.datastore.mapping.core.connections.ConnectionSourcesSupport;
-import org.grails.datastore.mapping.core.connections.DefaultConnectionSource;
-import org.grails.datastore.mapping.core.connections.InMemoryConnectionSources;
-import org.grails.datastore.mapping.core.connections.MultipleConnectionSourceCapableDatastore;
-import org.grails.datastore.mapping.core.connections.SingletonConnectionSources;
-import org.grails.datastore.mapping.core.exceptions.ConfigurationException;
-import org.grails.datastore.mapping.model.ClassMapping;
-import org.grails.datastore.mapping.model.EmbeddedPersistentEntity;
-import org.grails.datastore.mapping.model.MappingContext;
-import org.grails.datastore.mapping.model.PersistentEntity;
-import org.grails.datastore.mapping.model.PersistentProperty;
-import org.grails.datastore.mapping.model.PropertyMapping;
-import org.grails.datastore.mapping.mongo.config.MongoAttribute;
-import org.grails.datastore.mapping.mongo.config.MongoCollection;
-import org.grails.datastore.mapping.mongo.config.MongoMappingContext;
-import org.grails.datastore.mapping.mongo.config.MongoSettings;
-import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceFactory;
-import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceSettings;
-import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceSettingsBuilder;
-import org.grails.datastore.mapping.mongo.engine.codecs.PersistentEntityCodec;
-import org.grails.datastore.mapping.multitenancy.AllTenantsResolver;
-import org.grails.datastore.mapping.multitenancy.MultiTenancySettings;
-import org.grails.datastore.mapping.multitenancy.MultiTenantCapableDatastore;
-import org.grails.datastore.mapping.multitenancy.TenantResolver;
-import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundException;
-import org.grails.datastore.mapping.transactions.DatastoreTransactionManager;
-import org.grails.datastore.mapping.transactions.TransactionCapableDatastore;
-import org.grails.datastore.mapping.validation.ValidatorRegistry;
+package org.grails.datastore.mapping.mongo
+
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.TimeUnit
+
+import com.mongodb.MongoClientSettings
+import com.mongodb.MongoCommandException
+import com.mongodb.client.MongoClient
+import com.mongodb.client.MongoIterable
+import com.mongodb.client.model.IndexOptions
+import com.mongodb.connection.ClusterType
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import jakarta.annotation.PreDestroy
+import jakarta.persistence.FlushModeType
+import org.bson.Document
+import org.bson.codecs.Codec
+import org.bson.codecs.configuration.CodecProvider
+import org.bson.codecs.configuration.CodecRegistries
+import org.bson.codecs.configuration.CodecRegistry
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.MessageSource
+import org.springframework.context.SmartLifecycle
+import org.springframework.context.support.StaticMessageSource
+import org.springframework.core.env.PropertyResolver
+import org.springframework.transaction.PlatformTransactionManager
+
+import grails.gorm.multitenancy.Tenants
+import grails.util.GrailsMessageSourceUtils
+import org.grails.datastore.bson.codecs.CodecExtensions
+import org.grails.datastore.gorm.GormEnhancer
+import org.grails.datastore.gorm.GormInstanceApi
+import org.grails.datastore.gorm.GormValidationApi
+import org.grails.datastore.gorm.events.AutoTimestampEventListener
+import org.grails.datastore.gorm.events.ConfigurableApplicationEventPublisher
+import org.grails.datastore.gorm.events.DefaultApplicationEventPublisher
+import org.grails.datastore.gorm.events.DomainEventListener
+import org.grails.datastore.gorm.mongo.MongoGormEnhancer
+import org.grails.datastore.gorm.mongo.api.MongoStaticApi
+import org.grails.datastore.gorm.multitenancy.MultiTenantEventListener
+import org.grails.datastore.gorm.utils.ClasspathEntityScanner
+import org.grails.datastore.gorm.validation.constraints.MappingContextAwareConstraintFactory
+import org.grails.datastore.gorm.validation.constraints.builtin.UniqueConstraint
+import org.grails.datastore.gorm.validation.constraints.registry.ConstraintRegistry
+import org.grails.datastore.gorm.validation.listener.ValidationEventListener
+import org.grails.datastore.gorm.validation.registry.support.ValidatorRegistries
+import org.grails.datastore.mapping.config.Settings
+import org.grails.datastore.mapping.core.AbstractDatastore
+import org.grails.datastore.mapping.core.Datastore
+import org.grails.datastore.mapping.core.DatastoreUtils
+import org.grails.datastore.mapping.core.Session
+import org.grails.datastore.mapping.core.StatelessDatastore
+import org.grails.datastore.mapping.core.connections.ConnectionSource
+import org.grails.datastore.mapping.core.connections.ConnectionSources
+import org.grails.datastore.mapping.core.connections.ConnectionSourcesInitializer
+import org.grails.datastore.mapping.core.connections.ConnectionSourcesListener
+import org.grails.datastore.mapping.core.connections.ConnectionSourcesSupport
+import org.grails.datastore.mapping.core.connections.DefaultConnectionSource
+import org.grails.datastore.mapping.core.connections.InMemoryConnectionSources
+import org.grails.datastore.mapping.core.connections.MultipleConnectionSourceCapableDatastore
+import org.grails.datastore.mapping.core.connections.SingletonConnectionSources
+import org.grails.datastore.mapping.core.exceptions.ConfigurationException
+import org.grails.datastore.mapping.model.ClassMapping
+import org.grails.datastore.mapping.model.EmbeddedPersistentEntity
+import org.grails.datastore.mapping.model.MappingContext
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.datastore.mapping.model.PersistentProperty
+import org.grails.datastore.mapping.model.PropertyMapping
+import org.grails.datastore.mapping.mongo.config.MongoAttribute
+import org.grails.datastore.mapping.mongo.config.MongoCollection
+import org.grails.datastore.mapping.mongo.config.MongoMappingContext
+import org.grails.datastore.mapping.mongo.config.MongoSettings
+import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceFactory
+import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceSettings
+import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceSettingsBuilder
+import org.grails.datastore.mapping.mongo.engine.codecs.PersistentEntityCodec
+import org.grails.datastore.mapping.multitenancy.AllTenantsResolver
+import org.grails.datastore.mapping.multitenancy.MultiTenancySettings
+import org.grails.datastore.mapping.multitenancy.MultiTenantCapableDatastore
+import org.grails.datastore.mapping.multitenancy.TenantResolver
+import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundException
+import org.grails.datastore.mapping.transactions.DatastoreTransactionManager
+import org.grails.datastore.mapping.transactions.TransactionCapableDatastore
+import org.grails.datastore.mapping.validation.ValidatorRegistry
 
 /**
  * A Datastore implementation for the Mongo document store.
@@ -115,20 +104,21 @@ import org.grails.datastore.mapping.validation.ValidatorRegistry;
  * @author Graeme Rocher
  * @since 1.0
  */
-public class MongoDatastore extends AbstractDatastore implements MappingContext.Listener, Closeable, StatelessDatastore, MultipleConnectionSourceCapableDatastore, MultiTenantCapableDatastore<MongoClient, MongoConnectionSourceSettings>, TransactionCapableDatastore, SmartLifecycle {
-
-    public static final String SETTING_DATABASE_NAME = MongoSettings.SETTING_DATABASE_NAME;
-    public static final String SETTING_CONNECTION_STRING = MongoSettings.SETTING_CONNECTION_STRING;
-    public static final String SETTING_URL = MongoSettings.SETTING_URL;
-    public static final String SETTING_DEFAULT_MAPPING = MongoSettings.SETTING_DEFAULT_MAPPING;
-    public static final String SETTING_OPTIONS = MongoSettings.SETTING_OPTIONS;
-    public static final String SETTING_HOST = MongoSettings.SETTING_HOST;
-    public static final String SETTING_PORT = MongoSettings.SETTING_PORT;
-    public static final String SETTING_USERNAME = MongoSettings.SETTING_USERNAME;
-    public static final String SETTING_PASSWORD = MongoSettings.SETTING_PASSWORD;
-    public static final String SETTING_STATELESS = MongoSettings.SETTING_STATELESS;
-    public static final String SETTING_ENGINE = MongoSettings.SETTING_ENGINE;
-    public static final String INDEX_ATTRIBUTES = "indexAttributes";
+@CompileStatic
+class MongoDatastore extends AbstractDatastore implements MappingContext.Listener, Closeable, StatelessDatastore, MultipleConnectionSourceCapableDatastore, MultiTenantCapableDatastore<MongoClient, MongoConnectionSourceSettings>, TransactionCapableDatastore, SmartLifecycle {
+
+    public static final String SETTING_DATABASE_NAME = MongoSettings.SETTING_DATABASE_NAME
+    public static final String SETTING_CONNECTION_STRING = MongoSettings.SETTING_CONNECTION_STRING
+    public static final String SETTING_URL = MongoSettings.SETTING_URL
+    public static final String SETTING_DEFAULT_MAPPING = MongoSettings.SETTING_DEFAULT_MAPPING
+    public static final String SETTING_OPTIONS = MongoSettings.SETTING_OPTIONS
+    public static final String SETTING_HOST = MongoSettings.SETTING_HOST
+    public static final String SETTING_PORT = MongoSettings.SETTING_PORT
+    public static final String SETTING_USERNAME = MongoSettings.SETTING_USERNAME
+    public static final String SETTING_PASSWORD = MongoSettings.SETTING_PASSWORD
+    public static final String SETTING_STATELESS = MongoSettings.SETTING_STATELESS
+    public static final String SETTING_ENGINE = MongoSettings.SETTING_ENGINE
+    public static final String INDEX_ATTRIBUTES = 'indexAttributes'
 
     /**
      * TTL index attribute. MongoDB's {@link IndexOptions#expireAfter(Long, TimeUnit)} is the only
@@ -137,43 +127,52 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * is pulled out of the index attributes and applied explicitly. TTL indexes are single-field
      * only — MongoDB silently ignores the option on a compound index.
      */
-    public static final String INDEX_EXPIRE_AFTER_SECONDS = "expireAfterSeconds";
+    public static final String INDEX_EXPIRE_AFTER_SECONDS = 'expireAfterSeconds'
 
     /**
      * Opt-in index attribute: when an index already exists on the same keys with conflicting
      * options that cannot be reconciled in place (i.e. anything other than a TTL change), drop the
      * existing index and recreate it with the declared options instead of just logging the conflict.
      */
-    public static final String INDEX_RECREATE_ON_CONFLICT = "recreateOnConflict";
+    public static final String INDEX_RECREATE_ON_CONFLICT = 'recreateOnConflict'
 
     /** MongoDB server error code for {@code IndexOptionsConflict}. */
-    private static final int INDEX_OPTIONS_CONFLICT_CODE = 85;
-    public static final String CODEC_ENGINE = MongoConstants.CODEC_ENGINE;
+    private static final int INDEX_OPTIONS_CONFLICT_CODE = 85
+    public static final String CODEC_ENGINE = MongoConstants.CODEC_ENGINE
+
+    /**
+     * Below the web server's phase so the client outlives the requests using it, and above
+     * {@code EmbeddedMongoLifecycle.PHASE} so an embedded server outlives this client:
+     * Spring starts in ascending phase order and stops in descending.
+     */
+    public static final int LIFECYCLE_PHASE = -1000
 
     /**
      * Not final because {@link #start()} replaces it after a CRaC restore. Everything other
      * than construction reaches it through {@link #getMongoClient()}, so a replacement is
      * picked up without anything else having to be told.
      */
-    protected volatile MongoClient mongo;
-    protected final String defaultDatabase;
-    protected final Map<PersistentEntity, String> mongoCollections = new ConcurrentHashMap<>();
-    protected final Map<PersistentEntity, String> mongoDatabases = new ConcurrentHashMap<>();
-    protected final boolean stateless;
-    protected final boolean codecEngine;
-    protected final boolean transactionsEnabled;
-    private volatile Boolean transactionsSupported;
-    private volatile boolean warnedTransactionsUnsupported = false;
-    protected CodecRegistry codecRegistry;
-    protected final ConfigurableApplicationEventPublisher eventPublisher;
-    protected final PlatformTransactionManager transactionManager;
-    protected final GormEnhancer gormEnhancer;
-    protected final ConnectionSources<MongoClient, MongoConnectionSourceSettings> connectionSources;
-    protected final FlushModeType defaultFlushMode;
-    protected final Map<String, MongoDatastore> datastoresByConnectionSource = new LinkedHashMap<>();
-    protected final MultiTenancySettings.MultiTenancyMode multiTenancyMode;
-    protected final TenantResolver tenantResolver;
-    protected final AutoTimestampEventListener autoTimestampEventListener;
+    protected volatile MongoClient mongo
+    protected final String defaultDatabase
+    protected final Map<PersistentEntity, String> mongoCollections = new ConcurrentHashMap<>()
+    protected final Map<PersistentEntity, String> mongoDatabases = new ConcurrentHashMap<>()
+    protected final boolean stateless
+    protected final boolean codecEngine
+    protected final boolean transactionsEnabled
+    private volatile Boolean transactionsSupported
+    private volatile boolean warnedTransactionsUnsupported = false
+    protected CodecRegistry codecRegistry
+    protected final ConfigurableApplicationEventPublisher eventPublisher
+    protected final PlatformTransactionManager transactionManager
+    protected final GormEnhancer gormEnhancer
+    protected final ConnectionSources<MongoClient, MongoConnectionSourceSettings> connectionSources
+    protected final FlushModeType defaultFlushMode
+    protected final Map<String, MongoDatastore> datastoresByConnectionSource = new LinkedHashMap<>()
+    protected final MultiTenancySettings.MultiTenancyMode multiTenancyMode
+    protected final TenantResolver tenantResolver
+    protected final AutoTimestampEventListener autoTimestampEventListener
+
+    private volatile boolean running = true
 
     /**
      * Configures a new {@link MongoDatastore} for the given arguments
@@ -182,117 +181,117 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param mappingContext The mapping context
      */
-    public MongoDatastore(final ConnectionSources<MongoClient, MongoConnectionSourceSettings> connectionSources, final MongoMappingContext mappingContext, final ConfigurableApplicationEventPublisher eventPublisher) {
-        super(mappingContext, connectionSources != null ? connectionSources.getBaseConfiguration() : null, null);
+    MongoDatastore(final ConnectionSources<MongoClient, MongoConnectionSourceSettings> connectionSources, final MongoMappingContext mappingContext, final ConfigurableApplicationEventPublisher eventPublisher) {
+        super(mappingContext, connectionSources != null ? connectionSources.getBaseConfiguration() : null, null)
         if (connectionSources == null) {
-            throw new IllegalArgumentException("Argument [connectionSources] cannot be null");
+            throw new IllegalArgumentException('Argument [connectionSources] cannot be null')
         }
         if (mappingContext == null) {
-            throw new IllegalArgumentException("Argument [mappingContext] cannot be null");
+            throw new IllegalArgumentException('Argument [mappingContext] cannot be null')
         }
 
-        this.connectionSources = connectionSources;
+        this.connectionSources = connectionSources
 
-        final ConnectionSource<MongoClient, MongoConnectionSourceSettings> defaultConnectionSource = connectionSources.getDefaultConnectionSource();
-        MongoConnectionSourceSettings settings = defaultConnectionSource.getSettings();
-        MultiTenancySettings multiTenancySettings = settings.getMultiTenancy();
+        final ConnectionSource<MongoClient, MongoConnectionSourceSettings> defaultConnectionSource = connectionSources.getDefaultConnectionSource()
+        MongoConnectionSourceSettings settings = defaultConnectionSource.getSettings()
+        MultiTenancySettings multiTenancySettings = settings.getMultiTenancy()
 
-        this.mongo = defaultConnectionSource.getSource();
-        this.multiTenancyMode = multiTenancySettings.getMode();
-        this.eventPublisher = eventPublisher;
-        this.defaultDatabase = settings.getDatabase();
-        this.defaultFlushMode = settings.getFlushMode();
-        this.stateless = settings.isStateless();
-        this.codecEngine = settings.getEngine().equals(MongoConstants.CODEC_ENGINE);
-        this.transactionsEnabled = settings.isTransactional();
+        this.mongo = defaultConnectionSource.getSource()
+        this.multiTenancyMode = multiTenancySettings.getMode()
+        this.eventPublisher = eventPublisher
+        this.defaultDatabase = settings.getDatabase()
+        this.defaultFlushMode = settings.getFlushMode()
+        this.stateless = settings.isStateless()
+        this.codecEngine = settings.getEngine() == MongoConstants.CODEC_ENGINE
+        this.transactionsEnabled = settings.isTransactional()
         codecRegistry = CodecRegistries.fromRegistries(
                 CodecRegistries.fromProviders(new CodecExtensions(), new PersistentEntityCodeRegistry()),
                 mappingContext.getCodecRegistry(),
                 MongoClientSettings.getDefaultCodecRegistry()
-        );
+        )
 
-        DatastoreTransactionManager datastoreTransactionManager = new DatastoreTransactionManager();
-        datastoreTransactionManager.setDatastore(this);
-        transactionManager = datastoreTransactionManager;
+        DatastoreTransactionManager datastoreTransactionManager = new DatastoreTransactionManager()
+        datastoreTransactionManager.setDatastore(this)
+        transactionManager = datastoreTransactionManager
         for (PersistentEntity entity : mappingContext.getPersistentEntities()) {
-            registerEntity(entity);
+            registerEntity(entity)
         }
         if (!(connectionSources instanceof SingletonConnectionSources)) {
-            final MongoDatastore parent = this;
-            Iterable<ConnectionSource<MongoClient, MongoConnectionSourceSettings>> allConnectionSources = connectionSources.getAllConnectionSources();
+            final MongoDatastore parent = this
+            Iterable<ConnectionSource<MongoClient, MongoConnectionSourceSettings>> allConnectionSources = connectionSources.getAllConnectionSources()
             for (final ConnectionSource<MongoClient, MongoConnectionSourceSettings> connectionSource : allConnectionSources) {
-                SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings> singletonConnectionSources = new SingletonConnectionSources<>(connectionSource, connectionSources.getBaseConfiguration());
-                MongoDatastore childDatastore;
+                SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings> singletonConnectionSources = new SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings>(connectionSource, connectionSources.getBaseConfiguration())
+                MongoDatastore childDatastore
 
-                if (ConnectionSource.DEFAULT.equals(connectionSource.getName())) {
-                    childDatastore = this;
+                if (ConnectionSource.DEFAULT == connectionSource.getName()) {
+                    childDatastore = this
                 } else {
-                    childDatastore = createChildDatastore(mappingContext, eventPublisher, parent, singletonConnectionSources);
+                    childDatastore = createChildDatastore(mappingContext, eventPublisher, parent, singletonConnectionSources)
                 }
-                datastoresByConnectionSource.put(connectionSource.getName(), childDatastore);
+                datastoresByConnectionSource.put(connectionSource.getName(), childDatastore)
             }
 
-            connectionSources.addListener(new ConnectionSourcesListener<>() {
-                public void newConnectionSource(final ConnectionSource<MongoClient, MongoConnectionSourceSettings> connectionSource) {
-                    final SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings> singletonConnectionSources = new SingletonConnectionSources<>(connectionSource, connectionSources.getBaseConfiguration());
-                    MongoDatastore childDatastore = createChildDatastore(mappingContext, eventPublisher, parent, singletonConnectionSources);
-                    datastoresByConnectionSource.put(connectionSource.getName(), childDatastore);
-                    registerAllEntitiesWithEnhancer();
+            connectionSources.addListener(new ConnectionSourcesListener<MongoClient, MongoConnectionSourceSettings>() {
+                void newConnectionSource(final ConnectionSource<MongoClient, MongoConnectionSourceSettings> connectionSource) {
+                    final SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings> singletonConnectionSources = new SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings>(connectionSource, connectionSources.getBaseConfiguration())
+                    MongoDatastore childDatastore = createChildDatastore(mappingContext, eventPublisher, parent, singletonConnectionSources)
+                    datastoresByConnectionSource.put(connectionSource.getName(), childDatastore)
+                    registerAllEntitiesWithEnhancer()
                 }
-            });
+            })
         }
 
         if (multiTenancyMode == MultiTenancySettings.MultiTenancyMode.SCHEMA) {
-            final TenantResolver baseResolver = multiTenancySettings.getTenantResolver();
+            final TenantResolver baseResolver = multiTenancySettings.getTenantResolver()
             this.tenantResolver = new AllTenantsResolver() {
                 @Override
-                public Iterable<Serializable> resolveTenantIds() {
-                    List<Serializable> ids = new ArrayList<>();
-                    MongoIterable<String> databaseNames = defaultConnectionSource.getSource().listDatabaseNames();
+                Iterable<Serializable> resolveTenantIds() {
+                    List<Serializable> ids = new ArrayList<>()
+                    MongoIterable<String> databaseNames = defaultConnectionSource.getSource().listDatabaseNames()
                     for (String databaseName : databaseNames) {
-                        ids.add(databaseName);
+                        ids.add(databaseName)
                     }
-                    return ids;
+                    return ids
                 }
 
                 @Override
-                public Serializable resolveTenantIdentifier() throws TenantNotFoundException {
-                    return baseResolver.resolveTenantIdentifier();
+                Serializable resolveTenantIdentifier() throws TenantNotFoundException {
+                    return baseResolver.resolveTenantIdentifier()
                 }
-            };
+            }
         } else {
-            this.tenantResolver = multiTenancySettings.getTenantResolver();
+            this.tenantResolver = multiTenancySettings.getTenantResolver()
         }
 
-        this.autoTimestampEventListener = new AutoTimestampEventListener(this);
-        registerEventListeners(this.eventPublisher);
-        this.gormEnhancer = initialize(settings);
+        this.autoTimestampEventListener = new AutoTimestampEventListener(this)
+        registerEventListeners(this.eventPublisher)
+        this.gormEnhancer = initialize(settings)
     }
 
-    private MongoDatastore createChildDatastore(MongoMappingContext mappingContext,
-                                                    ConfigurableApplicationEventPublisher eventPublisher,
-                                                    final MongoDatastore parent,
-                                                    SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings> singletonConnectionSources) {
+    @PackageScope
+    MongoDatastore createChildDatastore(MongoMappingContext mappingContext,
+                                        ConfigurableApplicationEventPublisher eventPublisher,
+                                        final MongoDatastore parent,
+                                        SingletonConnectionSources<MongoClient, MongoConnectionSourceSettings> singletonConnectionSources) {
         return new MongoDatastore(singletonConnectionSources, mappingContext, eventPublisher) {
             @Override
             protected MongoGormEnhancer initialize(final MongoConnectionSourceSettings settings) {
-                super.buildIndex();
-                return null;
+                super.buildIndex()
+                return null
             }
 
             @Override
-            public MongoDatastore getDatastoreForConnection(String connectionName) {
-                if (connectionName.equals(Settings.SETTING_DATASOURCE) || connectionName.equals(ConnectionSource.DEFAULT)) {
-                    return parent;
-                } else {
-                    MongoDatastore mongoDatastore = parent.datastoresByConnectionSource.get(connectionName);
-                    if (mongoDatastore == null) {
-                        throw new ConfigurationException("DataSource not found for name [" + connectionName + "] in configuration. Please check your multiple data sources configuration and try again.");
-                    }
-                    return mongoDatastore;
+            MongoDatastore getDatastoreForConnection(String connectionName) {
+                if (connectionName == Settings.SETTING_DATASOURCE || connectionName == ConnectionSource.DEFAULT) {
+                    return parent
                 }
+                MongoDatastore mongoDatastore = parent.datastoresByConnectionSource.get(connectionName)
+                if (mongoDatastore == null) {
+                    throw new ConfigurationException('DataSource not found for name [' + connectionName + '] in configuration. Please check your multiple data sources configuration and try again.')
+                }
+                return mongoDatastore
             }
-        };
+        }
     }
 
     /**
@@ -302,8 +301,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param classes The persistent classes
      */
-    public MongoDatastore(ConnectionSources<MongoClient, MongoConnectionSourceSettings> connectionSources, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
-        this(connectionSources, createMappingContext(connectionSources, classes), eventPublisher);
+    MongoDatastore(ConnectionSources<MongoClient, MongoConnectionSourceSettings> connectionSources, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
+        this(connectionSources, createMappingContext(connectionSources, classes), eventPublisher)
     }
 
     /**
@@ -313,9 +312,9 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param mappingContext The mapping context
      */
-    public MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, MongoMappingContext mappingContext, ConfigurableApplicationEventPublisher eventPublisher) {
+    MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, MongoMappingContext mappingContext, ConfigurableApplicationEventPublisher eventPublisher) {
         // The client is supplied by the caller, so GORM must not close it (closeable = false).
-        this(createDefaultConnectionSources(mongoClient, configuration, mappingContext, false), mappingContext, eventPublisher);
+        this(createDefaultConnectionSources(mongoClient, configuration, mappingContext, false), mappingContext, eventPublisher)
     }
 
     /**
@@ -325,8 +324,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param classes The persistent classes
      */
-    public MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
-        this(mongoClient, configuration, createMappingContext(configuration, classes), eventPublisher);
+    MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
+        this(mongoClient, configuration, createMappingContext(configuration, classes), eventPublisher)
     }
 
     /**
@@ -336,8 +335,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param packages The packages to scan
      */
-    public MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Package... packages) {
-        this(mongoClient, configuration, createMappingContext(configuration, new ClasspathEntityScanner().scan(packages)), eventPublisher);
+    MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Package... packages) {
+        this(mongoClient, configuration, createMappingContext(configuration, new ClasspathEntityScanner().scan(packages)), eventPublisher)
     }
 
     /**
@@ -346,8 +345,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param mongoClient The {@link MongoClient} instance
      * @param classes The persistent classes
      */
-    public MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, Class... classes) {
-        this(mongoClient, configuration, createMappingContext(configuration, classes), new DefaultApplicationEventPublisher());
+    MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, Class... classes) {
+        this(mongoClient, configuration, createMappingContext(configuration, classes), new DefaultApplicationEventPublisher())
     }
 
     /**
@@ -356,8 +355,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param mongoClient The {@link MongoClient} instance
      * @param packages The packages to scan
      */
-    public MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, Package... packages) {
-        this(mongoClient, configuration, createMappingContext(configuration, new ClasspathEntityScanner().scan(packages)), new DefaultApplicationEventPublisher());
+    MongoDatastore(MongoClient mongoClient, PropertyResolver configuration, Package... packages) {
+        this(mongoClient, configuration, createMappingContext(configuration, new ClasspathEntityScanner().scan(packages)), new DefaultApplicationEventPublisher())
     }
 
     /**
@@ -366,8 +365,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param mongoClient The {@link MongoClient} instance
      * @param classes The persistent classes
      */
-    public MongoDatastore(MongoClient mongoClient, Class... classes) {
-        this(mongoClient, mapToPropertyResolver(null), createMappingContext(mapToPropertyResolver(null), classes), new DefaultApplicationEventPublisher());
+    MongoDatastore(MongoClient mongoClient, Class... classes) {
+        this(mongoClient, mapToPropertyResolver((Map) null), createMappingContext(mapToPropertyResolver((Map) null), classes), new DefaultApplicationEventPublisher())
     }
 
     /**
@@ -378,9 +377,9 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param mappingContext The mapping context
      */
-    public MongoDatastore(MongoClientSettings.Builder clientOptions, PropertyResolver configuration, MongoMappingContext mappingContext, ConfigurableApplicationEventPublisher eventPublisher) {
+    MongoDatastore(MongoClientSettings.Builder clientOptions, PropertyResolver configuration, MongoMappingContext mappingContext, ConfigurableApplicationEventPublisher eventPublisher) {
         // GORM builds the client from the supplied options, so it owns it and must close it (closeable = true).
-        this(createDefaultConnectionSources(createMongoClient(configuration, clientOptions, mappingContext), configuration, mappingContext, true), mappingContext, eventPublisher);
+        this(createDefaultConnectionSources(createMongoClient(configuration, clientOptions, mappingContext), configuration, mappingContext, true), mappingContext, eventPublisher)
     }
 
     /**
@@ -390,9 +389,9 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param configuration The configuration
      * @param mappingContext The mapping context
      */
-    public MongoDatastore(MongoClientSettings.Builder clientOptions, PropertyResolver configuration, MongoMappingContext mappingContext) {
+    MongoDatastore(MongoClientSettings.Builder clientOptions, PropertyResolver configuration, MongoMappingContext mappingContext) {
         // GORM builds the client from the supplied options, so it owns it and must close it (closeable = true).
-        this(createDefaultConnectionSources(createMongoClient(configuration, clientOptions, mappingContext), configuration, mappingContext, true), mappingContext, new DefaultApplicationEventPublisher());
+        this(createDefaultConnectionSources(createMongoClient(configuration, clientOptions, mappingContext), configuration, mappingContext, true), mappingContext, new DefaultApplicationEventPublisher())
     }
 
     /**
@@ -402,8 +401,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param mappingContext The mapping context
      */
-    public MongoDatastore(PropertyResolver configuration, MongoMappingContext mappingContext, ConfigurableApplicationEventPublisher eventPublisher) {
-        this(ConnectionSourcesInitializer.create(new MongoConnectionSourceFactory(), configuration), mappingContext, eventPublisher);
+    MongoDatastore(PropertyResolver configuration, MongoMappingContext mappingContext, ConfigurableApplicationEventPublisher eventPublisher) {
+        this(ConnectionSourcesInitializer.create(new MongoConnectionSourceFactory(), configuration), mappingContext, eventPublisher)
     }
 
     /**
@@ -414,8 +413,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param connectionSourceFactory The connection source factory to use
      * @param classes The persistent classes
      */
-    public MongoDatastore(PropertyResolver configuration, MongoConnectionSourceFactory connectionSourceFactory, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
-        this(ConnectionSourcesInitializer.create(connectionSourceFactory, configuration), eventPublisher, classes);
+    MongoDatastore(PropertyResolver configuration, MongoConnectionSourceFactory connectionSourceFactory, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
+        this(ConnectionSourcesInitializer.create(connectionSourceFactory, configuration), eventPublisher, classes)
     }
 
     /**
@@ -425,8 +424,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The Spring ApplicationContext
      * @param classes The persistent classes
      */
-    public MongoDatastore(PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
-        this(configuration, new MongoConnectionSourceFactory(), eventPublisher, classes);
+    MongoDatastore(PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
+        this(configuration, new MongoConnectionSourceFactory(), eventPublisher, classes)
     }
 
     /**
@@ -435,8 +434,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param configuration The configuration for the datastore
      * @param mappingContext The mapping context
      */
-    public MongoDatastore(PropertyResolver configuration, MongoMappingContext mappingContext) {
-        this(configuration, mappingContext, new DefaultApplicationEventPublisher());
+    MongoDatastore(PropertyResolver configuration, MongoMappingContext mappingContext) {
+        this(configuration, mappingContext, new DefaultApplicationEventPublisher())
     }
 
     /**
@@ -445,8 +444,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param configuration The configuration for the datastore
      * @param classes The persistent classes
      */
-    public MongoDatastore(PropertyResolver configuration, Class... classes) {
-        this(configuration, new DefaultApplicationEventPublisher(), classes);
+    MongoDatastore(PropertyResolver configuration, Class... classes) {
+        this(configuration, new DefaultApplicationEventPublisher(), classes)
     }
 
     /**
@@ -456,8 +455,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The event publisher
      * @param classes The persistent classes
      */
-    public MongoDatastore(Map<String, Object> configuration, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
-        this(mapToPropertyResolver(configuration), eventPublisher, classes);
+    MongoDatastore(Map<String, Object> configuration, ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
+        this(mapToPropertyResolver(configuration), eventPublisher, classes)
     }
 
     /**
@@ -466,8 +465,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param configuration The configuration
      * @param classes The persistent classes
      */
-    public MongoDatastore(Map<String, Object> configuration, Class... classes) {
-        this(mapToPropertyResolver(configuration), new DefaultApplicationEventPublisher(), classes);
+    MongoDatastore(Map<String, Object> configuration, Class... classes) {
+        this(mapToPropertyResolver(configuration), new DefaultApplicationEventPublisher(), classes)
     }
 
     /**
@@ -475,8 +474,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      *
      * @param configuration The configuration
      */
-    public MongoDatastore(Map<String, Object> configuration) {
-        this(configuration, new Class[0]);
+    MongoDatastore(Map<String, Object> configuration) {
+        this(configuration, new Class[0])
     }
 
     /**
@@ -485,9 +484,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param configuration The configuration
      * @param mappingContext The {@link MongoMappingContext}
      */
-
-    public MongoDatastore(Map<String, Object> configuration, MongoMappingContext mappingContext) {
-        this(mapToPropertyResolver(configuration), mappingContext, new DefaultApplicationEventPublisher());
+    MongoDatastore(Map<String, Object> configuration, MongoMappingContext mappingContext) {
+        this(mapToPropertyResolver(configuration), mappingContext, new DefaultApplicationEventPublisher())
     }
 
     /**
@@ -495,8 +493,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      *
      * @param mappingContext The {@link MongoMappingContext}
      */
-    public MongoDatastore(MongoMappingContext mappingContext) {
-        this(mapToPropertyResolver(null), mappingContext, new DefaultApplicationEventPublisher());
+    MongoDatastore(MongoMappingContext mappingContext) {
+        this(mapToPropertyResolver((Map) null), mappingContext, new DefaultApplicationEventPublisher())
     }
 
     /**
@@ -504,8 +502,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      *
      * @param classes The persistent classes
      */
-    public MongoDatastore(Class... classes) {
-        this(mapToPropertyResolver(null), classes);
+    MongoDatastore(Class... classes) {
+        this(mapToPropertyResolver((Map) null), classes)
     }
 
     /**
@@ -513,8 +511,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      *
      * @param packagesToScan The packages to scan
      */
-    public MongoDatastore(Package... packagesToScan) {
-        this(new ClasspathEntityScanner().scan(packagesToScan));
+    MongoDatastore(Package... packagesToScan) {
+        this(new ClasspathEntityScanner().scan(packagesToScan))
     }
 
     /**
@@ -522,8 +520,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      *
      * @param packageToScan The packages to scan
      */
-    public MongoDatastore(Package packageToScan) {
-        this(new ClasspathEntityScanner().scan(packageToScan));
+    MongoDatastore(Package packageToScan) {
+        this(new ClasspathEntityScanner().scan(packageToScan))
     }
 
     /**
@@ -532,8 +530,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param configuration The configuration
      * @param packagesToScan The packages to scan
      */
-    public MongoDatastore(PropertyResolver configuration, Package... packagesToScan) {
-        this(configuration, new ClasspathEntityScanner().scan(packagesToScan));
+    MongoDatastore(PropertyResolver configuration, Package... packagesToScan) {
+        this(configuration, new ClasspathEntityScanner().scan(packagesToScan))
     }
 
     /**
@@ -542,8 +540,8 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param configuration The configuration
      * @param packagesToScan The packages to scan
      */
-    public MongoDatastore(Map<String, Object> configuration, Package... packagesToScan) {
-        this(DatastoreUtils.createPropertyResolver(configuration), packagesToScan);
+    MongoDatastore(Map<String, Object> configuration, Package... packagesToScan) {
+        this(DatastoreUtils.createPropertyResolver(configuration), packagesToScan)
     }
 
     /**
@@ -553,27 +551,29 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param eventPublisher The event publisher
      * @param packagesToScan The packages to scan
      */
-    public MongoDatastore(PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Package... packagesToScan) {
-        this(configuration, eventPublisher, new ClasspathEntityScanner().scan(packagesToScan));
+    MongoDatastore(PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher, Package... packagesToScan) {
+        this(configuration, eventPublisher, new ClasspathEntityScanner().scan(packagesToScan))
     }
 
     /**
      * @return The {@link ConnectionSources} for this datastore
      */
-    public ConnectionSources<MongoClient, MongoConnectionSourceSettings> getConnectionSources() {
-        return connectionSources;
+    ConnectionSources<MongoClient, MongoConnectionSourceSettings> getConnectionSources() {
+        return connectionSources
     }
 
     /**
      * Builds the MongoDB index for this datastore
      */
-    public void buildIndex() {
-        for (PersistentEntity entity : this.mappingContext.getPersistentEntities()) {
+    void buildIndex() {
+        for (PersistentEntity entity : getMappingContext().getPersistentEntities()) {
             // Only create Mongo templates for entities that are mapped with Mongo
             if (!entity.isExternal()) {
-                if (entity.isMultiTenant() && multiTenancyMode == MultiTenancySettings.MultiTenancyMode.SCHEMA) continue;
+                if (entity.isMultiTenant() && multiTenancyMode == MultiTenancySettings.MultiTenancyMode.SCHEMA) {
+                    continue
+                }
 
-                initializeIndices(entity);
+                initializeIndices(entity)
             }
         }
     }
@@ -581,15 +581,15 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
     /**
      * @return The default flush mode
      */
-    public FlushModeType getDefaultFlushMode() {
-        return defaultFlushMode;
+    FlushModeType getDefaultFlushMode() {
+        return defaultFlushMode
     }
 
     /**
      * @return The default database name
      */
-    public String getDefaultDatabase() {
-        return defaultDatabase;
+    String getDefaultDatabase() {
+        return defaultDatabase
     }
 
     /**
@@ -598,10 +598,10 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param codecRegistries The {@link CodecRegistry} instances
      */
     @Autowired(required = false)
-    public void setCodecRegistries(List<CodecRegistry> codecRegistries) {
+    void setCodecRegistries(List<CodecRegistry> codecRegistries) {
         this.codecRegistry = CodecRegistries.fromRegistries(
                 this.codecRegistry,
-                CodecRegistries.fromRegistries(codecRegistries));
+                CodecRegistries.fromRegistries(codecRegistries))
     }
 
     /**
@@ -610,10 +610,10 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param codecProviders The {@link CodecProvider} instances
      */
     @Autowired(required = false)
-    public void setCodecProviders(List<CodecProvider> codecProviders) {
+    void setCodecProviders(List<CodecProvider> codecProviders) {
         this.codecRegistry = CodecRegistries.fromRegistries(
                 this.codecRegistry,
-                CodecRegistries.fromProviders(codecProviders));
+                CodecRegistries.fromProviders(codecProviders))
     }
 
     /**
@@ -622,10 +622,10 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param codecs The {@link Codec} instances
      */
     @Autowired(required = false)
-    public void setCodecs(List<Codec<?>> codecs) {
+    void setCodecs(List<Codec<?>> codecs) {
         this.codecRegistry = CodecRegistries.fromRegistries(
                 this.codecRegistry,
-                CodecRegistries.fromCodecs(codecs));
+                CodecRegistries.fromCodecs(codecs))
     }
 
     /**
@@ -634,28 +634,28 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param messageSources The message source
      */
     @Autowired(required = false)
-    public void setMessageSource(List<MessageSource> messageSources) {
-        setMessageSource(GrailsMessageSourceUtils.findPreferredMessageSource(messageSources));
+    void setMessageSource(List<MessageSource> messageSources) {
+        setMessageSource(GrailsMessageSourceUtils.findPreferredMessageSource(messageSources))
     }
 
-    public void setMessageSource(MessageSource messageSource) {
+    void setMessageSource(MessageSource messageSource) {
         if (messageSource != null) {
-            configureValidatorRegistry(connectionSources.getDefaultConnectionSource().getSettings(), (MongoMappingContext) mappingContext, messageSource);
+            configureValidatorRegistry(connectionSources.getDefaultConnectionSource().getSettings(), getMappingContext(), messageSource)
         }
     }
 
     /**
      * @return The transaction manager
      */
-    public PlatformTransactionManager getTransactionManager() {
-        return transactionManager;
+    PlatformTransactionManager getTransactionManager() {
+        return transactionManager
     }
 
     /**
      * @return The {@link CodecRegistry}
      */
-    public CodecRegistry getCodecRegistry() {
-        return codecRegistry;
+    CodecRegistry getCodecRegistry() {
+        return codecRegistry
     }
 
     /**
@@ -664,12 +664,11 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param entity The entity
      * @return The {@link PersistentEntityCodec}
      */
-    public PersistentEntityCodec getPersistentEntityCodec(PersistentEntity entity) {
+    PersistentEntityCodec getPersistentEntityCodec(PersistentEntity entity) {
         if (entity instanceof EmbeddedPersistentEntity) {
-            return new PersistentEntityCodec(codecRegistry, entity);
-        } else {
-            return getPersistentEntityCodec(entity.getJavaClass());
+            return new PersistentEntityCodec(codecRegistry, entity)
         }
+        return getPersistentEntityCodec(entity.getJavaClass())
     }
 
     /**
@@ -678,32 +677,32 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param entityClass The entity class
      * @return The {@link PersistentEntityCodec}
      */
-    public PersistentEntityCodec getPersistentEntityCodec(Class entityClass) {
+    PersistentEntityCodec getPersistentEntityCodec(Class entityClass) {
         if (entityClass == null) {
-            throw new IllegalArgumentException("Argument [entityClass] cannot be null");
+            throw new IllegalArgumentException('Argument [entityClass] cannot be null')
         }
 
-        final PersistentEntity entity = getMappingContext().getPersistentEntity(entityClass.getName());
+        final PersistentEntity entity = getMappingContext().getPersistentEntity(entityClass.getName())
         if (entity == null) {
-            throw new IllegalArgumentException("Argument [" + entityClass + "] is not an entity");
+            throw new IllegalArgumentException('Argument [' + entityClass + '] is not an entity')
         }
 
-        return (PersistentEntityCodec) getCodecRegistry().get(entity.getJavaClass());
+        return (PersistentEntityCodec) getCodecRegistry().get(entity.getJavaClass())
     }
 
     /**
      * @return The {@link ConfigurableApplicationEventPublisher} instance used by this datastore
      */
     @Override
-    public ConfigurableApplicationEventPublisher getApplicationEventPublisher() {
-        return this.eventPublisher;
+    ConfigurableApplicationEventPublisher getApplicationEventPublisher() {
+        return this.eventPublisher
     }
 
     /**
      * @return The {@link MongoClient} instance
      */
-    public MongoClient getMongoClient() {
-        return mongo;
+    MongoClient getMongoClient() {
+        return mongo
     }
 
     /**
@@ -716,56 +715,53 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @return {@code true} if server-side transactions should be used
      * @since 8.0
      */
-    public boolean isTransactionsEnabled() {
+    boolean isTransactionsEnabled() {
         if (!transactionsEnabled) {
-            return false;
+            return false
         }
         // Once the topology is positively known it does not change at runtime, so latch the result to
         // avoid recomputing (and possibly flipping) on every transaction begin.
-        Boolean supported = transactionsSupported;
+        Boolean supported = transactionsSupported
         if (supported != null) {
-            return supported;
+            return supported
         }
         try {
-            ClusterType clusterType = mongo.getClusterDescription().getType();
+            ClusterType clusterType = mongo.getClusterDescription().getType()
             switch (clusterType) {
-                case STANDALONE:
-                    transactionsSupported = Boolean.FALSE;
+                case ClusterType.STANDALONE:
+                    transactionsSupported = Boolean.FALSE
                     if (!warnedTransactionsUnsupported) {
-                        warnedTransactionsUnsupported = true;
-                        LOG.warn("grails.mongodb.transactional is enabled but the connected MongoDB topology is standalone, " +
-                                "which does not support multi-document transactions. Falling back to flush-only transaction behavior.");
+                        warnedTransactionsUnsupported = true
+                        LOG.warn('grails.mongodb.transactional is enabled but the connected MongoDB topology is standalone, ' +
+                                'which does not support multi-document transactions. Falling back to flush-only transaction behavior.')
                     }
-                    return false;
-                case REPLICA_SET:
-                case SHARDED:
-                case LOAD_BALANCED:
-                    transactionsSupported = Boolean.TRUE;
-                    return true;
+                    return false
+                case ClusterType.REPLICA_SET:
+                case ClusterType.SHARDED:
+                case ClusterType.LOAD_BALANCED:
+                    transactionsSupported = Boolean.TRUE
+                    return true
                 default:
                     // UNKNOWN: topology not discovered yet. Assume transactions are available for this
                     // attempt without latching, so a later definitive determination can still apply.
-                    return true;
+                    return true
             }
-        }
-        catch (RuntimeException e) {
-            LOG.debug("Could not determine MongoDB cluster topology for transaction support; assuming transactions are available: {}", e.getMessage(), e);
-            return true;
+        } catch (RuntimeException e) {
+            LOG.debug('Could not determine MongoDB cluster topology for transaction support; assuming transactions are available: {}', e.getMessage(), e)
+            return true
         }
     }
 
-    public String getDatabaseName(PersistentEntity entity) {
+    String getDatabaseName(PersistentEntity entity) {
         if (entity.isMultiTenant() && multiTenancyMode == MultiTenancySettings.MultiTenancyMode.SCHEMA) {
-            return Tenants.currentId(getClass()).toString();
+            return Tenants.currentId(getClass()).toString()
         }
-        else {
-            final String databaseName = mongoDatabases.get(entity);
-            if (databaseName == null) {
-                mongoDatabases.put(entity, defaultDatabase);
-                return defaultDatabase;
-            }
-            return databaseName;
+        final String databaseName = mongoDatabases.get(entity)
+        if (databaseName == null) {
+            mongoDatabases.put(entity, defaultDatabase)
+            return defaultDatabase
         }
+        return databaseName
     }
 
     /**
@@ -774,14 +770,14 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param entity The entity
      * @return The collection name
      */
-    public String getCollectionName(PersistentEntity entity) {
-        final String collectionName = mongoCollections.get(entity);
+    String getCollectionName(PersistentEntity entity) {
+        final String collectionName = mongoCollections.get(entity)
         if (collectionName == null) {
-            final String decapitalizedName = entity.isRoot() ? entity.getDecapitalizedName() : entity.getRootEntity().getDecapitalizedName();
-            mongoCollections.put(entity, decapitalizedName);
-            return decapitalizedName;
+            final String decapitalizedName = entity.isRoot() ? entity.getDecapitalizedName() : entity.getRootEntity().getDecapitalizedName()
+            mongoCollections.put(entity, decapitalizedName)
+            return decapitalizedName
         }
-        return collectionName;
+        return collectionName
     }
 
     /**
@@ -790,43 +786,41 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param entity The entity
      * @return The Mongo collection
      */
-    public com.mongodb.client.MongoCollection<Document> getCollection(PersistentEntity entity) {
+    com.mongodb.client.MongoCollection<Document> getCollection(PersistentEntity entity) {
         return getMongoClient()
                 .getDatabase(getDatabaseName(entity))
                 .getCollection(getCollectionName(entity))
-                .withCodecRegistry(codecRegistry);
+                .withCodecRegistry(codecRegistry)
     }
 
     /**
      * @return The mapping context
      */
     @Override
-    public MongoMappingContext getMappingContext() {
-        return (MongoMappingContext) super.getMappingContext();
+    MongoMappingContext getMappingContext() {
+        return (MongoMappingContext) super.getMappingContext()
     }
 
     @Override
-    public boolean isSchemaless() {
-        return true;
+    boolean isSchemaless() {
+        return true
     }
 
     protected void registerAllEntitiesWithEnhancer() {
-        for (PersistentEntity persistentEntity : mappingContext.getPersistentEntities()) {
-            gormEnhancer.registerEntity(persistentEntity);
+        for (PersistentEntity persistentEntity : getMappingContext().getPersistentEntities()) {
+            gormEnhancer.registerEntity(persistentEntity)
         }
     }
 
     @Override
     protected Session createSession(PropertyResolver connDetails) {
         if (stateless) {
-            return createStatelessSession(connDetails);
-        } else {
-            if (codecEngine) {
-                return new MongoCodecSession(this, getMappingContext(), getApplicationEventPublisher(), false);
-            } else {
-                return new MongoSession(this, getMappingContext(), getApplicationEventPublisher(), false);
-            }
+            return createStatelessSession(connDetails)
+        }
+        if (codecEngine) {
+            return new MongoCodecSession(this, getMappingContext(), getApplicationEventPublisher(), false)
         }
+        return new MongoSession(this, getMappingContext(), getApplicationEventPublisher(), false)
     }
 
     /**
@@ -834,84 +828,81 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param settings
      */
     protected MongoGormEnhancer initialize(final MongoConnectionSourceSettings settings) {
-        getMappingContext().addMappingContextListener(this);
-        initializeConverters(this.mappingContext);
+        getMappingContext().addMappingContextListener(this)
+        initializeConverters(getMappingContext())
 
-        this.mappingContext.addMappingContextListener(new MappingContext.Listener() {
+        getMappingContext().addMappingContextListener(new MappingContext.Listener() {
             @Override
-            public void persistentEntityAdded(PersistentEntity entity) {
-                gormEnhancer.registerEntity(entity);
-                registerEntity(entity);
+            void persistentEntityAdded(PersistentEntity entity) {
+                gormEnhancer.registerEntity(entity)
+                registerEntity(entity)
             }
-        });
+        })
 
-        buildIndex();
+        buildIndex()
 
         return new MongoGormEnhancer(this, transactionManager, settings) {
             @Override
             protected <D> MongoStaticApi<D> getStaticApi(Class<D> cls, String qualifier) {
-                MongoDatastore mongoDatastore = getDatastoreForQualifier(cls, qualifier);
-                return new MongoStaticApi<>(cls, mongoDatastore, createDynamicFinders(mongoDatastore), transactionManager);
+                MongoDatastore mongoDatastore = getDatastoreForQualifier(cls, qualifier)
+                return new MongoStaticApi<D>(cls, mongoDatastore, createDynamicFinders(mongoDatastore), transactionManager)
             }
 
             @Override
             protected <D> GormInstanceApi<D> getInstanceApi(Class<D> cls, String qualifier) {
-                MongoDatastore mongoDatastore = getDatastoreForQualifier(cls, qualifier);
+                MongoDatastore mongoDatastore = getDatastoreForQualifier(cls, qualifier)
 
-                GormInstanceApi<D> instanceApi = new GormInstanceApi<>(cls, mongoDatastore);
-                instanceApi.setFailOnError(getFailOnError());
-                instanceApi.setMarkDirty(getMarkDirty());
-                return instanceApi;
+                GormInstanceApi<D> instanceApi = new GormInstanceApi<D>(cls, mongoDatastore)
+                instanceApi.setFailOnError(getFailOnError())
+                instanceApi.setMarkDirty(getMarkDirty())
+                return instanceApi
             }
 
             @Override
             protected <D> GormValidationApi<D> getValidationApi(Class<D> cls, String qualifier) {
-                MongoDatastore mongoDatastore = getDatastoreForQualifier(cls, qualifier);
-                return new GormValidationApi<>(cls, mongoDatastore);
+                MongoDatastore mongoDatastore = getDatastoreForQualifier(cls, qualifier)
+                return new GormValidationApi<D>(cls, mongoDatastore)
             }
 
             private <D> MongoDatastore getDatastoreForQualifier(Class<D> cls, String qualifier) {
-                String defaultConnectionSourceName = ConnectionSourcesSupport.getDefaultConnectionSourceName(getMappingContext().getPersistentEntity(cls.getName()));
-                if (defaultConnectionSourceName.equals(ConnectionSource.ALL)) {
-                    defaultConnectionSourceName = ConnectionSource.DEFAULT;
+                String defaultConnectionSourceName = ConnectionSourcesSupport.getDefaultConnectionSourceName(getMappingContext().getPersistentEntity(cls.getName()))
+                if (defaultConnectionSourceName == ConnectionSource.ALL) {
+                    defaultConnectionSourceName = ConnectionSource.DEFAULT
                 }
 
-                boolean isDefaultQualifier = qualifier.equals(ConnectionSource.DEFAULT);
-                if (isDefaultQualifier && defaultConnectionSourceName.equals(ConnectionSource.DEFAULT)) {
-                    return MongoDatastore.this;
+                boolean isDefaultQualifier = qualifier == ConnectionSource.DEFAULT
+                if (isDefaultQualifier && defaultConnectionSourceName == ConnectionSource.DEFAULT) {
+                    return MongoDatastore.this
                 }
-                else {
-                    if (isDefaultQualifier) {
-                        qualifier = defaultConnectionSourceName;
-                    }
-                    ConnectionSource<MongoClient, MongoConnectionSourceSettings> connectionSource = connectionSources.getConnectionSource(qualifier);
-                    if (connectionSource == null) {
-                        throw new ConfigurationException("Invalid connection [" + defaultConnectionSourceName + "] configured for class [" + cls + "]");
-                    }
-
-                    return datastoresByConnectionSource.get(qualifier);
+                String resolvedQualifier = qualifier
+                if (isDefaultQualifier) {
+                    resolvedQualifier = defaultConnectionSourceName
+                }
+                ConnectionSource<MongoClient, MongoConnectionSourceSettings> connectionSource = connectionSources.getConnectionSource(resolvedQualifier)
+                if (connectionSource == null) {
+                    throw new ConfigurationException('Invalid connection [' + defaultConnectionSourceName + '] configured for class [' + cls + ']')
                 }
-            }
-        };
 
+                return datastoresByConnectionSource.get(resolvedQualifier)
+            }
+        }
     }
 
     @Override
     protected Session createStatelessSession(PropertyResolver connectionDetails) {
         if (codecEngine) {
-            return new MongoCodecSession(this, getMappingContext(), getApplicationEventPublisher(), true);
-        } else {
-            return new MongoSession(this, getMappingContext(), getApplicationEventPublisher(), true);
+            return new MongoCodecSession(this, getMappingContext(), getApplicationEventPublisher(), true)
         }
+        return new MongoSession(this, getMappingContext(), getApplicationEventPublisher(), true)
     }
 
     protected void registerEventListeners(ConfigurableApplicationEventPublisher eventPublisher) {
-        eventPublisher.addApplicationListener(new DomainEventListener(this));
-        eventPublisher.addApplicationListener(autoTimestampEventListener);
-        eventPublisher.addApplicationListener(new ValidationEventListener(this));
+        eventPublisher.addApplicationListener(new DomainEventListener(this))
+        eventPublisher.addApplicationListener(autoTimestampEventListener)
+        eventPublisher.addApplicationListener(new ValidationEventListener(this))
 
         if (multiTenancyMode == MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR) {
-            eventPublisher.addApplicationListener(new MultiTenantEventListener(this));
+            eventPublisher.addApplicationListener(new MultiTenantEventListener(this))
         }
     }
 
@@ -921,57 +912,55 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @param entity The entity
      */
     protected void initializeIndices(final PersistentEntity entity) {
-        final com.mongodb.client.MongoCollection<Document> collection = getCollection(entity);
-        final ClassMapping<MongoCollection> classMapping = entity.getMapping();
+        final com.mongodb.client.MongoCollection<Document> collection = getCollection(entity)
+        final ClassMapping<MongoCollection> classMapping = (ClassMapping<MongoCollection>) entity.getMapping()
         if (classMapping != null) {
-            final MongoCollection mappedForm = classMapping.getMappedForm();
+            final MongoCollection mappedForm = classMapping.getMappedForm()
             if (mappedForm != null) {
-                List<MongoCollection.Index> indices = mappedForm.getIndices();
+                List<MongoCollection.Index> indices = mappedForm.getIndices()
                 for (MongoCollection.Index index : indices) {
                     createOrUpdateIndex(entity, collection, new Document(index.getDefinition()),
-                            index.getOptions(), "with definition [" + index.getDefinition() + "]");
+                            index.getOptions(), 'with definition [' + index.getDefinition() + ']')
                 }
 
                 for (Map compoundIndex : mappedForm.getCompoundIndices()) {
-
-                    Map indexAttributes = null;
+                    Map indexAttributes = null
                     if (compoundIndex.containsKey(INDEX_ATTRIBUTES)) {
-                        Object o = compoundIndex.remove(INDEX_ATTRIBUTES);
+                        Object o = compoundIndex.remove(INDEX_ATTRIBUTES)
                         if (o instanceof Map) {
-                            indexAttributes = (Map) o;
+                            indexAttributes = (Map) o
                         }
                     }
-                    Document indexDef = new Document(compoundIndex);
+                    Document indexDef = new Document((Map<String, Object>) compoundIndex)
                     createOrUpdateIndex(entity, collection, indexDef, indexAttributes,
-                            "compound index with definition [" + indexDef + "]");
+                            'compound index with definition [' + indexDef + ']')
                 }
             }
         }
 
         for (PersistentProperty<MongoAttribute> property : entity.getPersistentProperties()) {
-            final boolean indexed = isIndexed(property);
+            final boolean indexed = isIndexed(property)
 
             if (indexed) {
-                final MongoAttribute mongoAttributeMapping = property.getMapping().getMappedForm();
-                Document dbObject = new Document();
-                final String fieldName = getMongoFieldNameForProperty(property);
-                dbObject.put(fieldName, 1);
-                Document options = new Document();
+                final MongoAttribute mongoAttributeMapping = property.getMapping().getMappedForm()
+                Document dbObject = new Document()
+                final String fieldName = getMongoFieldNameForProperty(property)
+                dbObject.put(fieldName, 1)
+                Document options = new Document()
                 if (mongoAttributeMapping != null) {
-                    Map attributes = mongoAttributeMapping.getIndexAttributes();
+                    Map attributes = mongoAttributeMapping.getIndexAttributes()
                     if (attributes != null) {
-                        attributes = new HashMap(attributes);
+                        attributes = new HashMap(attributes)
                         if (attributes.containsKey(MongoAttribute.INDEX_TYPE)) {
-                            dbObject.put(fieldName, attributes.remove(MongoAttribute.INDEX_TYPE));
+                            dbObject.put(fieldName, attributes.remove(MongoAttribute.INDEX_TYPE))
                         }
-                        options.putAll(attributes);
+                        options.putAll(attributes)
                     }
                 }
                 createOrUpdateIndex(entity, collection, dbObject, options,
-                        "on property [" + property.getName() + "]");
+                        'on property [' + property.getName() + ']')
             }
         }
-
     }
 
     /**
@@ -990,31 +979,31 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
     private void createOrUpdateIndex(PersistentEntity entity,
                                      com.mongodb.client.MongoCollection<Document> collection,
                                      Document keys, Map<String, Object> rawOptions, String descriptor) {
-        Map<String, Object> options = rawOptions != null ? new HashMap<>(rawOptions) : new HashMap<>();
+        Map<String, Object> options = rawOptions != null ? new HashMap<String, Object>(rawOptions) : new HashMap<String, Object>()
 
         // Control flag — not a Mongo index option.
-        boolean recreateOnConflict = Boolean.TRUE.equals(options.remove(INDEX_RECREATE_ON_CONFLICT));
+        boolean recreateOnConflict = Boolean.TRUE.equals(options.remove(INDEX_RECREATE_ON_CONFLICT))
 
-        Long expireAfterSeconds = null;
-        Object ttl = options.remove(INDEX_EXPIRE_AFTER_SECONDS);
+        Long expireAfterSeconds = null
+        Object ttl = options.remove(INDEX_EXPIRE_AFTER_SECONDS)
         if (ttl instanceof Number) {
-            expireAfterSeconds = ((Number) ttl).longValue();
+            expireAfterSeconds = ((Number) ttl).longValue()
         }
 
-        final IndexOptions indexOptions = MongoConstants.mapToObject(IndexOptions.class, options);
+        final IndexOptions indexOptions = MongoConstants.mapToObject(IndexOptions, options)
         if (expireAfterSeconds != null) {
-            indexOptions.expireAfter(expireAfterSeconds, TimeUnit.SECONDS);
+            indexOptions.expireAfter(expireAfterSeconds, TimeUnit.SECONDS)
         }
 
         try {
-            collection.createIndex(keys, indexOptions);
+            collection.createIndex(keys, indexOptions)
         } catch (MongoCommandException e) {
             if (e.getErrorCode() == INDEX_OPTIONS_CONFLICT_CODE) {
                 reconcileIndexConflict(entity, collection, keys, indexOptions,
-                        expireAfterSeconds, recreateOnConflict, descriptor, e);
+                        expireAfterSeconds, recreateOnConflict, descriptor, e)
             } else {
-                LOG.error("Failed to create index for entity [{}] {}: {}",
-                    entity.getName(), descriptor, e.getMessage(), e);
+                LOG.error('Failed to create index for entity [{}] {}: {}',
+                        entity.getName(), descriptor, e.getMessage(), e)
             }
         }
     }
@@ -1029,59 +1018,59 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
                                         com.mongodb.client.MongoCollection<Document> collection,
                                         Document keys, IndexOptions desired, Long expireAfterSeconds,
                                         boolean recreateOnConflict, String descriptor, MongoCommandException original) {
-        Document existing;
+        Document existing
         try {
-            existing = findIndexByKeyPattern(collection, keys);
+            existing = findIndexByKeyPattern(collection, keys)
         } catch (RuntimeException listError) {
-            LOG.error("Failed to create index for entity [{}] {} and could not inspect existing indexes: {}",
-                entity.getName(), descriptor, listError.getMessage(), original);
-            return;
+            LOG.error('Failed to create index for entity [{}] {} and could not inspect existing indexes: {}',
+                    entity.getName(), descriptor, listError.getMessage(), original)
+            return
         }
         if (existing == null) {
-            LOG.error("Failed to create index for entity [{}] {}: {}",
-                entity.getName(), descriptor, original.getMessage(), original);
-            return;
+            LOG.error('Failed to create index for entity [{}] {}: {}',
+                    entity.getName(), descriptor, original.getMessage(), original)
+            return
         }
 
-        String existingName = existing.getString("name");
-        Object existingTtl = existing.get(INDEX_EXPIRE_AFTER_SECONDS);
-        Long existingTtlSeconds = existingTtl instanceof Number ? ((Number) existingTtl).longValue() : null;
+        String existingName = existing.getString('name')
+        Object existingTtl = existing.get(INDEX_EXPIRE_AFTER_SECONDS)
+        Long existingTtlSeconds = existingTtl instanceof Number ? ((Number) existingTtl).longValue() : null
 
         // TTL change on an existing index — update in place, no rebuild, no gap.
-        boolean ttlChange = expireAfterSeconds != null && !expireAfterSeconds.equals(existingTtlSeconds);
+        boolean ttlChange = expireAfterSeconds != null && !expireAfterSeconds.equals(existingTtlSeconds)
         if (ttlChange) {
             try {
                 getMongoClient().getDatabase(getDatabaseName(entity))
-                        .runCommand(new Document("collMod", getCollectionName(entity))
-                                .append("index", new Document("name", existingName)
-                                        .append(INDEX_EXPIRE_AFTER_SECONDS, expireAfterSeconds)));
-                LOG.info("Updated TTL of index [{}] on entity [{}] to {}s",
-                    existingName, entity.getName(), expireAfterSeconds);
-                return;
+                        .runCommand(new Document('collMod', getCollectionName(entity))
+                                .append('index', new Document('name', existingName)
+                                        .append(INDEX_EXPIRE_AFTER_SECONDS, expireAfterSeconds)))
+                LOG.info('Updated TTL of index [{}] on entity [{}] to {}s',
+                        existingName, entity.getName(), expireAfterSeconds)
+                return
             } catch (MongoCommandException collModError) {
                 // collMod can't make every change (e.g. add a TTL to a non-TTL index on older
                 // servers) — fall through to recreate (if authorised) rather than fail outright.
-                LOG.warn("collMod TTL update failed for index [{}] on entity [{}]: {}{}",
-                    existingName, entity.getName(), collModError.getMessage(), recreateOnConflict ? " — recreating" : "");
+                LOG.warn('collMod TTL update failed for index [{}] on entity [{}]: {}{}',
+                        existingName, entity.getName(), collModError.getMessage(), recreateOnConflict ? ' — recreating' : '')
             }
         }
 
         if (recreateOnConflict) {
             try {
-                collection.dropIndex(existingName);
-                collection.createIndex(keys, desired);
-                LOG.info("Recreated index [{}] on entity [{}] {}", existingName, entity.getName(), descriptor);
+                collection.dropIndex(existingName)
+                collection.createIndex(keys, desired)
+                LOG.info('Recreated index [{}] on entity [{}] {}', existingName, entity.getName(), descriptor)
             } catch (MongoCommandException recreateError) {
-                LOG.error("Failed to recreate index [{}] on entity [{}] {}: {}",
-                    existingName, entity.getName(), descriptor, recreateError.getMessage(), recreateError);
+                LOG.error('Failed to recreate index [{}] on entity [{}] {}: {}',
+                        existingName, entity.getName(), descriptor, recreateError.getMessage(), recreateError)
             }
-            return;
+            return
         }
 
         LOG.error(
-            "Index conflict for entity [{}] {}: an index [{}] already exists on the same keys with different options. " +
-                "Declare indexAttributes:[recreateOnConflict:true] to drop and recreate it. Original error: {}",
-            entity.getName(), descriptor, existingName, original.getMessage());
+                'Index conflict for entity [{}] {}: an index [{}] already exists on the same keys with different options. ' +
+                        'Declare indexAttributes:[recreateOnConflict:true] to drop and recreate it. Original error: {}',
+                entity.getName(), descriptor, existingName, original.getMessage())
     }
 
     /**
@@ -1095,20 +1084,20 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * match it regardless of its key shape or name so {@code recreateOnConflict} can absorb it.</p>
      */
     private static Document findIndexByKeyPattern(com.mongodb.client.MongoCollection<Document> collection, Document keys) {
-        boolean desiredIsText = isTextIndex(keys);
+        boolean desiredIsText = isTextIndex(keys)
         for (Document idx : collection.listIndexes()) {
-            Object key = idx.get("key");
+            Object key = idx.get('key')
             if (!(key instanceof Document)) {
-                continue;
+                continue
             }
             if (desiredIsText && isTextIndex((Document) key)) {
-                return idx;
+                return idx
             }
             if (sameKeyPattern((Document) key, keys)) {
-                return idx;
+                return idx
             }
         }
-        return null;
+        return null
     }
 
     /**
@@ -1116,63 +1105,55 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * synthetic key MongoDB reports for an existing one ({@code {_fts: 'text', _ftsx: 1}}).
      */
     private static boolean isTextIndex(Document key) {
-        if (key.containsKey("_fts")) {
-            return true;
+        if (key.containsKey('_fts')) {
+            return true
         }
         for (Object v : key.values()) {
-            if ("text".equals(v)) {
-                return true;
+            if ('text' == v) {
+                return true
             }
         }
-        return false;
+        return false
     }
 
     private static boolean sameKeyPattern(Document existingKey, Document desiredKey) {
         if (existingKey.size() != desiredKey.size()) {
-            return false;
+            return false
         }
         for (Map.Entry<String, Object> entry : desiredKey.entrySet()) {
             if (!existingKey.containsKey(entry.getKey())) {
-                return false;
+                return false
             }
-            Object a = existingKey.get(entry.getKey());
-            Object b = entry.getValue();
+            Object a = existingKey.get(entry.getKey())
+            Object b = entry.getValue()
             if (a instanceof Number && b instanceof Number) {
                 if (((Number) a).doubleValue() != ((Number) b).doubleValue()) {
-                    return false;
+                    return false
                 }
             } else if (!Objects.equals(a, b)) {
-                return false;
+                return false
             }
         }
-        return true;
+        return true
     }
 
+    @PackageScope
     String getMongoFieldNameForProperty(PersistentProperty<MongoAttribute> property) {
-        PropertyMapping<MongoAttribute> pm = property.getMapping();
-        String propKey = null;
+        PropertyMapping<MongoAttribute> pm = property.getMapping()
+        String propKey = null
         if (pm.getMappedForm() != null) {
-            propKey = pm.getMappedForm().getField();
+            propKey = pm.getMappedForm().getField()
         }
         if (propKey == null) {
-            propKey = property.getName();
+            propKey = property.getName()
         }
-        return propKey;
+        return propKey
     }
 
-    public void persistentEntityAdded(PersistentEntity entity) {
-        initializeIndices(entity);
+    void persistentEntityAdded(PersistentEntity entity) {
+        initializeIndices(entity)
     }
 
-    /**
-     * Below the web server's phase so the client outlives the requests using it, and above
-     * {@code EmbeddedMongoLifecycle.PHASE} so an embedded server outlives this client:
-     * Spring starts in ascending phase order and stops in descending.
-     */
-    public static final int LIFECYCLE_PHASE = -1000;
-
-    private volatile boolean running = true;
-
     /**
      * Closes the {@link MongoClient} so the process can be checkpointed.
      *
@@ -1186,12 +1167,12 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * {@link #start()} does not later replace something it does not own.
      */
     @Override
-    public void stop() {
+    void stop() {
         if (!this.running || !ownsClient()) {
-            return;
+            return
         }
-        this.mongo.close();
-        this.running = false;
+        this.mongo.close()
+        this.running = false
     }
 
     /**
@@ -1199,24 +1180,24 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * configuration the original was built from, so settings applied at startup still apply.
      */
     @Override
-    public void start() {
+    void start() {
         if (this.running) {
-            return;
+            return
         }
         this.mongo = connectionSources.getFactory()
                 .create(ConnectionSource.DEFAULT, connectionSources.getBaseConfiguration())
-                .getSource();
-        this.running = true;
+                .getSource()
+        this.running = true
     }
 
     @Override
-    public boolean isRunning() {
-        return this.running;
+    boolean isRunning() {
+        return this.running
     }
 
     @Override
-    public int getPhase() {
-        return LIFECYCLE_PHASE;
+    int getPhase() {
+        return LIFECYCLE_PHASE
     }
 
     /**
@@ -1224,36 +1205,35 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * in by the application.
      */
     private boolean ownsClient() {
-        ConnectionSource<MongoClient, MongoConnectionSourceSettings> source = connectionSources.getDefaultConnectionSource();
-        return !(source instanceof DefaultConnectionSource) || ((DefaultConnectionSource<?, ?>) source).isCloseable();
+        ConnectionSource<MongoClient, MongoConnectionSourceSettings> source = connectionSources.getDefaultConnectionSource()
+        return !(source instanceof DefaultConnectionSource) || ((DefaultConnectionSource) source).isCloseable()
     }
 
     @Override
     @PreDestroy
-    public void close() {
-        MongoClient current = this.mongo;
+    void close() {
+        MongoClient current = this.mongo
         try {
-            super.destroy();
-        } catch (Exception e) {
+            super.destroy()
+        } catch (Exception ignored) {
             // ignore
         }
         try {
             if (connectionSources != null) {
-                connectionSources.close();
+                connectionSources.close()
             }
             // connectionSources closes the client it was built with, which is no longer the
             // one in use once a restore has replaced it.
             if (current != null && ownsClient()) {
-                current.close();
+                current.close()
             }
         } catch (IOException e) {
-            LOG.error("There was an error shutting down GORM for an entity: " + e.getMessage(), e);
+            LOG.error('There was an error shutting down GORM for an entity: ' + e.getMessage(), e)
         } finally {
-
             if (gormEnhancer != null) {
                 try {
-                    gormEnhancer.close();
-                } catch (Throwable e) {
+                    gormEnhancer.close()
+                } catch (Throwable ignored) {
                     // Ignore
                 }
             }
@@ -1273,130 +1253,130 @@ public class MongoDatastore extends AbstractDatastore implements MappingContext.
      * @return The {@link ConnectionSources}
      */
     protected static ConnectionSources<MongoClient, MongoConnectionSourceSettings> createDefaultConnectionSources(MongoClient mongoClient, PropertyResolver configuration, MongoMappingContext mappingContext, boolean closeable) {
-        MongoConnectionSourceSettings settings = new MongoConnectionSourceSettings();
-        settings.setDatabaseName(mappingContext.getDefaultDatabaseName());
-        ConnectionSource<MongoClient, MongoConnectionSourceSettings> defaultConnectionSource = new DefaultConnectionSource<>(ConnectionSource.DEFAULT, mongoClient, settings, closeable);
-        return new InMemoryConnectionSources<>(defaultConnectionSource, new MongoConnectionSourceFactory(), configuration);
+        MongoConnectionSourceSettings settings = new MongoConnectionSourceSettings()
+        settings.setDatabaseName(mappingContext.getDefaultDatabaseName())
+        ConnectionSource<MongoClient, MongoConnectionSourceSettings> defaultConnectionSource = new DefaultConnectionSource<MongoClient, MongoConnectionSourceSettings>(ConnectionSource.DEFAULT, mongoClient, settings, closeable)
+        return new InMemoryConnectionSources<MongoClient, MongoConnectionSourceSettings>(defaultConnectionSource, new MongoConnectionSourceFactory(), configuration)
     }
 
     protected static MongoClient createMongoClient(PropertyResolver configuration, MongoClientSettings.Builder mongoOptions, MongoMappingContext mappingContext) {
-        MongoConnectionSourceFactory mongoConnectionSourceFactory = new MongoConnectionSourceFactory();
-        mongoConnectionSourceFactory.setClientOptionsBuilder(mongoOptions);
-        return mongoConnectionSourceFactory.create(ConnectionSource.DEFAULT, configuration).getSource();
+        MongoConnectionSourceFactory mongoConnectionSourceFactory = new MongoConnectionSourceFactory()
+        mongoConnectionSourceFactory.setClientOptionsBuilder(mongoOptions)
+        return mongoConnectionSourceFactory.create(ConnectionSource.DEFAULT, configuration).getSource()
     }
 
     protected static MongoMappingContext createMappingContext(ConnectionSources<MongoClient, MongoConnectionSourceSettings> connectionSources, Class... classes) {
-        ConnectionSource<MongoClient, MongoConnectionSourceSettings> defaultConnectionSource = connectionSources.getDefaultConnectionSource();
-        MongoMappingContext mongoMappingContext = new MongoMappingContext(defaultConnectionSource.getSettings(), classes);
-        configureValidationRegistry(connectionSources.getDefaultConnectionSource().getSettings(), mongoMappingContext);
-        return mongoMappingContext;
+        ConnectionSource<MongoClient, MongoConnectionSourceSettings> defaultConnectionSource = connectionSources.getDefaultConnectionSource()
+        MongoMappingContext mongoMappingContext = new MongoMappingContext(defaultConnectionSource.getSettings(), classes)
+        configureValidationRegistry(connectionSources.getDefaultConnectionSource().getSettings(), mongoMappingContext)
+        return mongoMappingContext
     }
 
     protected static MongoMappingContext createMappingContext(PropertyResolver configuration, Class... classes) {
-        MongoConnectionSourceSettingsBuilder builder = new MongoConnectionSourceSettingsBuilder(configuration);
-        MongoConnectionSourceSettings mongoConnectionSourceSettings = builder.build();
-        MongoMappingContext mongoMappingContext = new MongoMappingContext(mongoConnectionSourceSettings, classes);;
-        configureValidationRegistry(mongoConnectionSourceSettings, mongoMappingContext);
-        return mongoMappingContext;
+        MongoConnectionSourceSettingsBuilder builder = new MongoConnectionSourceSettingsBuilder(configuration)
+        MongoConnectionSourceSettings mongoConnectionSourceSettings = builder.build()
+        MongoMappingContext mongoMappingContext = new MongoMappingContext(mongoConnectionSourceSettings, classes)
+        configureValidationRegistry(mongoConnectionSourceSettings, mongoMappingContext)
+        return mongoMappingContext
     }
 
     protected void registerEntity(PersistentEntity entity) {
-        String collectionName = entity.isRoot() ? entity.getDecapitalizedName() : entity.getRootEntity().getDecapitalizedName();
-        String databaseName = this.defaultDatabase;
+        String collectionName = entity.isRoot() ? entity.getDecapitalizedName() : entity.getRootEntity().getDecapitalizedName()
+        String databaseName = this.defaultDatabase
 
-        MongoCollection collectionMapping = (MongoCollection) entity.getMapping().getMappedForm();
+        MongoCollection collectionMapping = (MongoCollection) entity.getMapping().getMappedForm()
         if (collectionMapping.getCollection() != null) {
-            collectionName = collectionMapping.getCollection();
+            collectionName = collectionMapping.getCollection()
         }
         if (collectionMapping.getDatabase() != null) {
-            databaseName = collectionMapping.getDatabase();
+            databaseName = collectionMapping.getDatabase()
         }
 
-        mongoCollections.put(entity, collectionName);
-        mongoDatabases.put(entity, databaseName);
+        mongoCollections.put(entity, collectionName)
+        mongoDatabases.put(entity, databaseName)
     }
 
     private static void configureValidationRegistry(MongoConnectionSourceSettings settings, MongoMappingContext mongoMappingContext) {
-        MessageSource messageSource = new StaticMessageSource();
-        configureValidatorRegistry(settings, mongoMappingContext, messageSource);
+        MessageSource messageSource = new StaticMessageSource()
+        configureValidatorRegistry(settings, mongoMappingContext, messageSource)
     }
 
     private static void configureValidatorRegistry(MongoConnectionSourceSettings settings, MongoMappingContext mongoMappingContext, MessageSource messageSource) {
-        ValidatorRegistry validatorRegistry = ValidatorRegistries.createValidatorRegistry(mongoMappingContext, settings, messageSource);
+        ValidatorRegistry validatorRegistry = ValidatorRegistries.createValidatorRegistry(mongoMappingContext, settings, messageSource)
         if (validatorRegistry instanceof ConstraintRegistry) {
             ((ConstraintRegistry) validatorRegistry).addConstraintFactory(
-                    new MappingContextAwareConstraintFactory(UniqueConstraint.class, messageSource, mongoMappingContext)
-            );
+                    new MappingContextAwareConstraintFactory(UniqueConstraint, messageSource, mongoMappingContext)
+            )
         }
         mongoMappingContext.setValidatorRegistry(
                 validatorRegistry
-        );
+        )
     }
 
     @Override
-    public MultiTenancySettings.MultiTenancyMode getMultiTenancyMode() {
-        return this.multiTenancyMode;
+    MultiTenancySettings.MultiTenancyMode getMultiTenancyMode() {
+        return this.multiTenancyMode
     }
 
     @Override
-    public TenantResolver getTenantResolver() {
-        return this.tenantResolver;
+    TenantResolver getTenantResolver() {
+        return this.tenantResolver
     }
 
     @Override
-    public MongoDatastore getDatastoreForTenantId(Serializable tenantId) {
+    MongoDatastore getDatastoreForTenantId(Serializable tenantId) {
         if (getMultiTenancyMode() == MultiTenancySettings.MultiTenancyMode.DATABASE) {
-            return this.datastoresByConnectionSource.get(tenantId.toString());
+            return this.datastoresByConnectionSource.get(tenantId.toString())
         }
-        return this;
+        return this
     }
 
     @Override
-    public Datastore getDatastoreForConnection(String connectionName) {
-        if (connectionName.equals(Settings.SETTING_DATASOURCE) || connectionName.equals(ConnectionSource.DEFAULT)) {
-            return this;
-        } else {
-            MongoDatastore mongoDatastore = this.datastoresByConnectionSource.get(connectionName);
-            if (mongoDatastore == null) {
-                throw new ConfigurationException("DataSource not found for name [" + connectionName + "] in configuration. Please check your multiple data sources configuration and try again.");
-            }
-            return mongoDatastore;
+    Datastore getDatastoreForConnection(String connectionName) {
+        if (connectionName == Settings.SETTING_DATASOURCE || connectionName == ConnectionSource.DEFAULT) {
+            return this
         }
+        MongoDatastore mongoDatastore = this.datastoresByConnectionSource.get(connectionName)
+        if (mongoDatastore == null) {
+            throw new ConfigurationException('DataSource not found for name [' + connectionName + '] in configuration. Please check your multiple data sources configuration and try again.')
+        }
+        return mongoDatastore
     }
 
     @Override
-    public <T1> T1 withNewSession(Serializable tenantId, Closure<T1> callable) {
-        MongoDatastore mongoDatastore = getDatastoreForTenantId(tenantId);
-        Session session = mongoDatastore.connect();
+    def <T1> T1 withNewSession(Serializable tenantId, Closure<T1> callable) {
+        MongoDatastore mongoDatastore = getDatastoreForTenantId(tenantId)
+        Session session = mongoDatastore.connect()
         try {
-            DatastoreUtils.bindNewSession(session);
-            return callable.call(session);
-        }
-        finally {
-            DatastoreUtils.unbindSession(session);
+            DatastoreUtils.bindNewSession(session)
+            return callable.call(session)
+        } finally {
+            DatastoreUtils.unbindSession(session)
         }
     }
 
     class PersistentEntityCodeRegistry implements CodecProvider {
 
-        Map<String, PersistentEntityCodec> codecs = new HashMap<>();
+        Map<String, PersistentEntityCodec> codecs = new HashMap<>()
 
         @Override
-        public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
-            final String entityName = clazz.getName();
-            PersistentEntityCodec codec = codecs.get(entityName);
+        def <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
+            final String entityName = clazz.getName()
+            PersistentEntityCodec codec = codecs.get(entityName)
             if (codec == null) {
-                final PersistentEntity entity = getMappingContext().getPersistentEntity(entityName);
+                final PersistentEntity entity = getMappingContext().getPersistentEntity(entityName)
                 if (entity != null) {
-                    codec = new PersistentEntityCodec(codecRegistry, entity);
-                    codecs.put(entityName, codec);
+                    codec = new PersistentEntityCodec(codecRegistry, entity)
+                    codecs.put(entityName, codec)
                 }
             }
-            return codec;
+            return (Codec<T>) codec
         }
+
     }
 
-    public AutoTimestampEventListener getAutoTimestampEventListener() {
-        return this.autoTimestampEventListener;
+    AutoTimestampEventListener getAutoTimestampEventListener() {
+        return this.autoTimestampEventListener
     }
+
 }


(grails-core) 13/14: Convert grails-data-hibernate7-dbmigration-core connection and diff packages to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit de56a76d002e6a82e41bf5d551db1e401ed35df2
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 22:12:55 2026 -0500

    Convert grails-data-hibernate7-dbmigration-core connection and diff packages to Groovy
    
    Converts liquibase.ext.hibernate.database.connection (HibernateConnection,
    HibernateConnectionMetadata, HibernateDriver) and liquibase.ext.hibernate.diff
    (the seven Hibernate-aware ChangeGenerator subclasses) to @CompileStatic
    Groovy. Headers are left exactly as they were (only the two ASF-headered
    diff generators carry one). Stream/Optional pipelines become plain loops and
    null checks with identical short-circuit semantics.
    
    Verified with checkstyleMain/codenarcMain and the full
    grails-data-hibernate7-dbmigration-core (25 tests) and
    grails-data-hibernate7-dbmigration (86 tests) suites.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 .../database/connection/HibernateConnection.groovy | 358 ++++++++
 .../database/connection/HibernateConnection.java   | 360 --------
 .../connection/HibernateConnectionMetadata.groovy  | 913 +++++++++++++++++++++
 .../connection/HibernateConnectionMetadata.java    | 910 --------------------
 .../database/connection/HibernateDriver.groovy     |  62 ++
 .../database/connection/HibernateDriver.java       |  60 --
 .../HibernateChangedColumnChangeGenerator.groovy   | 107 +++
 .../HibernateChangedColumnChangeGenerator.java     |  92 ---
 ...ibernateChangedForeignKeyChangeGenerator.groovy |  49 ++
 .../HibernateChangedForeignKeyChangeGenerator.java |  46 --
 ...ibernateChangedPrimaryKeyChangeGenerator.groovy |  48 ++
 .../HibernateChangedPrimaryKeyChangeGenerator.java |  45 -
 ...HibernateChangedSequenceChangeGenerator.groovy} |  94 ++-
 ...eChangedUniqueConstraintChangeGenerator.groovy} |  41 +-
 .../HibernateMissingSequenceChangeGenerator.groovy |  40 +
 .../HibernateMissingSequenceChangeGenerator.java   |  37 -
 .../HibernateUnexpectedIndexChangeGenerator.groovy |  42 +
 .../HibernateUnexpectedIndexChangeGenerator.java   |  39 -
 18 files changed, 1690 insertions(+), 1653 deletions(-)

diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.groovy
new file mode 100644
index 0000000000..3758624002
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.groovy
@@ -0,0 +1,358 @@
+package liquibase.ext.hibernate.database.connection
+
+import java.nio.charset.StandardCharsets
+import java.sql.Array
+import java.sql.Blob
+import java.sql.CallableStatement
+import java.sql.Clob
+import java.sql.Connection
+import java.sql.DatabaseMetaData
+import java.sql.NClob
+import java.sql.PreparedStatement
+import java.sql.SQLException
+import java.sql.SQLFeatureNotSupportedException
+import java.sql.SQLWarning
+import java.sql.SQLXML
+import java.sql.Savepoint
+import java.sql.Statement
+import java.sql.Struct
+import java.util.concurrent.Executor
+
+import groovy.transform.CompileStatic
+import liquibase.resource.ResourceAccessor
+
+/**
+ * Implements java.sql.Connection in order to pretend a hibernate configuration is a database in order to fit into the Liquibase framework.
+ * Beyond standard Connection methods, this class exposes {@link #getPrefix()}, {@link #getPath()} and {@link #getProperties()} to access the setting passed in the JDBC URL.
+ */
+@CompileStatic
+class HibernateConnection implements Connection {
+
+    private final String prefix
+    private final String url
+
+    private String path
+    private final ResourceAccessor resourceAccessor
+    private final Properties properties
+
+    HibernateConnection(String url, ResourceAccessor resourceAccessor) {
+        this.url = url
+
+        this.prefix = url.replaceFirst(':[^:]+$', '')
+
+        // Trim the prefix off the URL for the path
+        path = url.substring(prefix.length() + 1)
+        this.resourceAccessor = resourceAccessor
+
+        // Check if there is a parameter/query string value.
+        properties = new Properties()
+
+        int queryIndex = path.indexOf('?')
+        if (queryIndex >= 0) {
+            // Convert the query string into properties
+            properties.putAll(readProperties(path.substring(queryIndex + 1)))
+
+            if (properties.containsKey('dialect') && !properties.containsKey('hibernate.dialect')) {
+                properties.put('hibernate.dialect', properties.getProperty('dialect'))
+            }
+
+            // Remove the query string
+            path = path.substring(0, queryIndex)
+        }
+    }
+
+    /**
+     * Creates properties to attach to this connection based on the passed query string.
+     */
+    protected final Properties readProperties(String queryString) {
+        Properties properties = new Properties()
+        String propertiesString = queryString.replaceAll('&', System.lineSeparator())
+        try {
+            propertiesString = URLDecoder.decode(propertiesString, StandardCharsets.UTF_8)
+            properties.load(new StringReader(propertiesString))
+        } catch (IOException ioe) {
+            throw new IllegalStateException('Failed to read properties from url', ioe)
+        }
+
+        return properties
+    }
+
+    /**
+     * Returns the entire connection URL
+     */
+    String getUrl() {
+        return url
+    }
+
+    /**
+     * Returns the 'protocol' of the URL. For example, "hibernate:classic" or "hibernate:ejb3"
+     */
+    String getPrefix() {
+        return prefix
+    }
+
+    /**
+     * The portion of the url between the path and the query string. Normally a filename or a class name.
+     */
+    String getPath() {
+        return path
+    }
+
+    /**
+     * The set of properties provided by the URL. Eg:
+     * <p/>
+     * <code>hibernate:classic:/path/to/hibernate.cfg.xml?foo=bar</code>
+     * <p/>
+     * This will have a property called 'foo' with a value of 'bar'.
+     */
+    Properties getProperties() {
+        return properties
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    /// JDBC METHODS
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    Statement createStatement() throws SQLException {
+        throw new SQLFeatureNotSupportedException()
+    }
+
+    @Override
+    PreparedStatement prepareStatement(String sql) throws SQLException {
+        throw new SQLFeatureNotSupportedException()
+    }
+
+    @Override
+    CallableStatement prepareCall(String sql) throws SQLException {
+        throw new SQLFeatureNotSupportedException()
+    }
+
+    @Override
+    String nativeSQL(String sql) throws SQLException {
+        throw new SQLFeatureNotSupportedException()
+    }
+
+    @Override
+    void setAutoCommit(boolean autoCommit) {}
+
+    @Override
+    boolean getAutoCommit() {
+        return false
+    }
+
+    @Override
+    void commit() {}
+
+    @Override
+    void rollback() {}
+
+    @Override
+    void close() {}
+
+    @Override
+    boolean isClosed() {
+        return false
+    }
+
+    @Override
+    DatabaseMetaData getMetaData() {
+        return new HibernateConnectionMetadata(url)
+    }
+
+    @Override
+    void setReadOnly(boolean readOnly) {}
+
+    @Override
+    boolean isReadOnly() {
+        return true
+    }
+
+    @Override
+    void setCatalog(String catalog) {}
+
+    @Override
+    String getCatalog() {
+        return 'HIBERNATE'
+    }
+
+    @Override
+    void setTransactionIsolation(int level) {}
+
+    @Override
+    int getTransactionIsolation() {
+        return Connection.TRANSACTION_NONE
+    }
+
+    @Override
+    SQLWarning getWarnings() {
+        return null
+    }
+
+    @Override
+    void clearWarnings() {}
+
+    @Override
+    Statement createStatement(int resultSetType, int resultSetConcurrency) {
+        return null
+    }
+
+    @Override
+    PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) {
+        return null
+    }
+
+    @Override
+    CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) {
+        return null
+    }
+
+    @Override
+    Map<String, Class<?>> getTypeMap() {
+        return (Map<String, Class<?>>) Collections.emptyMap()
+    }
+
+    @Override
+    void setTypeMap(Map<String, Class<?>> map) {}
+
+    @Override
+    void setHoldability(int holdability) {}
+
+    @Override
+    int getHoldability() {
+        return 0
+    }
+
+    @Override
+    Savepoint setSavepoint() {
+        return null
+    }
+
+    @Override
+    Savepoint setSavepoint(String name) {
+        return null
+    }
+
+    @Override
+    void rollback(Savepoint savepoint) {}
+
+    @Override
+    void releaseSavepoint(Savepoint savepoint) {}
+
+    @Override
+    Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
+        return null
+    }
+
+    @Override
+    PreparedStatement prepareStatement(
+            String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
+        return null
+    }
+
+    @Override
+    CallableStatement prepareCall(
+            String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
+        return null
+    }
+
+    @Override
+    PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) {
+        return null
+    }
+
+    @Override
+    PreparedStatement prepareStatement(String sql, int[] columnIndexes) {
+        return null
+    }
+
+    @Override
+    PreparedStatement prepareStatement(String sql, String[] columnNames) {
+        return null
+    }
+
+    @Override
+    Clob createClob() {
+        return null
+    }
+
+    @Override
+    Blob createBlob() {
+        return null
+    }
+
+    @Override
+    NClob createNClob() {
+        return null
+    }
+
+    @Override
+    SQLXML createSQLXML() {
+        return null
+    }
+
+    @Override
+    boolean isValid(int timeout) {
+        return false
+    }
+
+    @Override
+    void setClientInfo(String name, String value) {}
+
+    @Override
+    void setClientInfo(Properties properties) {}
+
+    @Override
+    String getClientInfo(String name) {
+        return null
+    }
+
+    @Override
+    Properties getClientInfo() {
+        return new Properties()
+    }
+
+    @Override
+    Array createArrayOf(String typeName, Object[] elements) {
+        return null
+    }
+
+    @Override
+    Struct createStruct(String typeName, Object[] attributes) {
+        return null
+    }
+
+    @Override
+    def <T> T unwrap(Class<T> iface) {
+        return null
+    }
+
+    @Override
+    boolean isWrapperFor(Class<?> iface) {
+        return false
+    }
+
+    @Override
+    void abort(Executor arg0) {}
+
+    @Override
+    int getNetworkTimeout() {
+        return 0
+    }
+
+    @Override
+    String getSchema() {
+        return 'HIBERNATE'
+    }
+
+    @Override
+    void setNetworkTimeout(Executor arg0, int arg1) {}
+
+    @Override
+    void setSchema(String arg0) {}
+
+    ResourceAccessor getResourceAccessor() {
+        return resourceAccessor
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.java
deleted file mode 100644
index 70667a08cd..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.java
+++ /dev/null
@@ -1,360 +0,0 @@
-package liquibase.ext.hibernate.database.connection;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.sql.Array;
-import java.sql.Blob;
-import java.sql.CallableStatement;
-import java.sql.Clob;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.NClob;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.sql.SQLWarning;
-import java.sql.SQLXML;
-import java.sql.Savepoint;
-import java.sql.Statement;
-import java.sql.Struct;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.Executor;
-
-import liquibase.resource.ResourceAccessor;
-
-/**
- * Implements java.sql.Connection in order to pretend a hibernate configuration is a database in order to fit into the Liquibase framework.
- * Beyond standard Connection methods, this class exposes {@link #getPrefix()}, {@link #getPath()} and {@link #getProperties()} to access the setting passed in the JDBC URL.
- */
-public class HibernateConnection implements Connection {
-    private final String prefix;
-    private final String url;
-
-    private String path;
-    private final ResourceAccessor resourceAccessor;
-    private final Properties properties;
-
-    public HibernateConnection(String url, ResourceAccessor resourceAccessor) {
-        this.url = url;
-
-        this.prefix = url.replaceFirst(":[^:]+$", "");
-
-        // Trim the prefix off the URL for the path
-        path = url.substring(prefix.length() + 1);
-        this.resourceAccessor = resourceAccessor;
-
-        // Check if there is a parameter/query string value.
-        properties = new Properties();
-
-        int queryIndex = path.indexOf('?');
-        if (queryIndex >= 0) {
-            // Convert the query string into properties
-            properties.putAll(readProperties(path.substring(queryIndex + 1)));
-
-            if (properties.containsKey("dialect") && !properties.containsKey("hibernate.dialect")) {
-                properties.put("hibernate.dialect", properties.getProperty("dialect"));
-            }
-
-            // Remove the query string
-            path = path.substring(0, queryIndex);
-        }
-    }
-
-    /**
-     * Creates properties to attach to this connection based on the passed query string.
-     */
-    protected final Properties readProperties(String queryString) {
-        Properties properties = new Properties();
-        String propertiesString = queryString.replaceAll("&", System.lineSeparator());
-        try {
-            propertiesString = URLDecoder.decode(propertiesString, StandardCharsets.UTF_8);
-            properties.load(new StringReader(propertiesString));
-        } catch (IOException ioe) {
-            throw new IllegalStateException("Failed to read properties from url", ioe);
-        }
-
-        return properties;
-    }
-
-    /**
-     * Returns the entire connection URL
-     */
-    public String getUrl() {
-        return url;
-    }
-
-    /**
-     * Returns the 'protocol' of the URL. For example, "hibernate:classic" or "hibernate:ejb3"
-     */
-    public String getPrefix() {
-        return prefix;
-    }
-
-    /**
-     * The portion of the url between the path and the query string. Normally a filename or a class name.
-     */
-    public String getPath() {
-        return path;
-    }
-
-    /**
-     * The set of properties provided by the URL. Eg:
-     * <p/>
-     * <code>hibernate:classic:/path/to/hibernate.cfg.xml?foo=bar</code>
-     * <p/>
-     * This will have a property called 'foo' with a value of 'bar'.
-     */
-    public Properties getProperties() {
-        return properties;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-    /// JDBC METHODS
-    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public Statement createStatement() throws SQLException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql) throws SQLException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    @Override
-    public CallableStatement prepareCall(String sql) throws SQLException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    @Override
-    public String nativeSQL(String sql) throws SQLException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    @Override
-    public void setAutoCommit(boolean autoCommit) {}
-
-    @Override
-    public boolean getAutoCommit() {
-        return false;
-    }
-
-    @Override
-    public void commit() {}
-
-    @Override
-    public void rollback() {}
-
-    @Override
-    public void close() {}
-
-    @Override
-    public boolean isClosed() {
-        return false;
-    }
-
-    @Override
-    public DatabaseMetaData getMetaData() {
-        return new HibernateConnectionMetadata(url);
-    }
-
-    @Override
-    public void setReadOnly(boolean readOnly) {}
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override
-    public void setCatalog(String catalog) {}
-
-    @Override
-    public String getCatalog() {
-        return "HIBERNATE";
-    }
-
-    @Override
-    public void setTransactionIsolation(int level) {}
-
-    @Override
-    public int getTransactionIsolation() {
-        return Connection.TRANSACTION_NONE;
-    }
-
-    @Override
-    public SQLWarning getWarnings() {
-        return null;
-    }
-
-    @Override
-    public void clearWarnings() {}
-
-    @Override
-    public Statement createStatement(int resultSetType, int resultSetConcurrency) {
-        return null;
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) {
-        return null;
-    }
-
-    @Override
-    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) {
-        return null;
-    }
-
-    @Override
-    public Map<String, Class<?>> getTypeMap() {
-        return Collections.emptyMap();
-    }
-
-    @Override
-    public void setTypeMap(Map<String, Class<?>> map) {}
-
-    @Override
-    public void setHoldability(int holdability) {}
-
-    @Override
-    public int getHoldability() {
-        return 0;
-    }
-
-    @Override
-    public Savepoint setSavepoint() {
-        return null;
-    }
-
-    @Override
-    public Savepoint setSavepoint(String name) {
-        return null;
-    }
-
-    @Override
-    public void rollback(Savepoint savepoint) {}
-
-    @Override
-    public void releaseSavepoint(Savepoint savepoint) {}
-
-    @Override
-    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
-        return null;
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(
-            String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
-        return null;
-    }
-
-    @Override
-    public CallableStatement prepareCall(
-            String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
-        return null;
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) {
-        return null;
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) {
-        return null;
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, String[] columnNames) {
-        return null;
-    }
-
-    @Override
-    public Clob createClob() {
-        return null;
-    }
-
-    @Override
-    public Blob createBlob() {
-        return null;
-    }
-
-    @Override
-    public NClob createNClob() {
-        return null;
-    }
-
-    @Override
-    public SQLXML createSQLXML() {
-        return null;
-    }
-
-    @Override
-    public boolean isValid(int timeout) {
-        return false;
-    }
-
-    @Override
-    public void setClientInfo(String name, String value) {}
-
-    @Override
-    public void setClientInfo(Properties properties) {}
-
-    @Override
-    public String getClientInfo(String name) {
-        return null;
-    }
-
-    @Override
-    public Properties getClientInfo() {
-        return new Properties();
-    }
-
-    @Override
-    public Array createArrayOf(String typeName, Object[] elements) {
-        return null;
-    }
-
-    @Override
-    public Struct createStruct(String typeName, Object[] attributes) {
-        return null;
-    }
-
-    @Override
-    public <T> T unwrap(Class<T> iface) {
-        return null;
-    }
-
-    @Override
-    public boolean isWrapperFor(Class<?> iface) {
-        return false;
-    }
-
-    @Override
-    public void abort(Executor arg0) {}
-
-    @Override
-    public int getNetworkTimeout() {
-        return 0;
-    }
-
-    @Override
-    public String getSchema() {
-        return "HIBERNATE";
-    }
-
-    @Override
-    public void setNetworkTimeout(Executor arg0, int arg1) {}
-
-    @Override
-    public void setSchema(String arg0) {}
-
-    public ResourceAccessor getResourceAccessor() {
-        return resourceAccessor;
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.groovy
new file mode 100644
index 0000000000..5d7c0fcec8
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.groovy
@@ -0,0 +1,913 @@
+package liquibase.ext.hibernate.database.connection
+
+import java.sql.Connection
+import java.sql.DatabaseMetaData
+import java.sql.ResultSet
+import java.sql.RowIdLifetime
+
+import groovy.transform.CompileStatic
+import org.hibernate.Version
+
+/**
+ * Implements the standard java.sql.DatabaseMetaData interface to allow the Hibernate integration to better fit into
+ * what Liquibase expects.
+ */
+@CompileStatic
+class HibernateConnectionMetadata implements DatabaseMetaData {
+
+    private final String url
+
+    HibernateConnectionMetadata(String url) {
+        this.url = url
+    }
+
+    @Override
+    boolean allProceduresAreCallable() {
+        return false
+    }
+
+    @Override
+    boolean allTablesAreSelectable() {
+        return false
+    }
+
+    @Override
+    String getURL() {
+        return url
+    }
+
+    @Override
+    String getUserName() {
+        return null
+    }
+
+    @Override
+    boolean isReadOnly() {
+        return true
+    }
+
+    @Override
+    boolean nullsAreSortedHigh() {
+        return false
+    }
+
+    @Override
+    boolean nullsAreSortedLow() {
+        return false
+    }
+
+    @Override
+    boolean nullsAreSortedAtStart() {
+        return false
+    }
+
+    @Override
+    boolean nullsAreSortedAtEnd() {
+        return false
+    }
+
+    @Override
+    String getDatabaseProductName() {
+        return 'Hibernate'
+    }
+
+    @Override
+    String getDatabaseProductVersion() {
+        return Version.getVersionString()
+    }
+
+    @Override
+    String getDriverName() {
+        return null
+    }
+
+    @Override
+    String getDriverVersion() {
+        return '0'
+    }
+
+    @Override
+    int getDriverMajorVersion() {
+        return 0
+    }
+
+    @Override
+    int getDriverMinorVersion() {
+        return 0
+    }
+
+    @Override
+    boolean usesLocalFiles() {
+        return false
+    }
+
+    @Override
+    boolean usesLocalFilePerTable() {
+        return false
+    }
+
+    @Override
+    boolean supportsMixedCaseIdentifiers() {
+        return false
+    }
+
+    @Override
+    boolean storesUpperCaseIdentifiers() {
+        return false
+    }
+
+    @Override
+    boolean storesLowerCaseIdentifiers() {
+        return false
+    }
+
+    @Override
+    boolean storesMixedCaseIdentifiers() {
+        return false
+    }
+
+    @Override
+    boolean supportsMixedCaseQuotedIdentifiers() {
+        return false
+    }
+
+    @Override
+    boolean storesUpperCaseQuotedIdentifiers() {
+        return false
+    }
+
+    @Override
+    boolean storesLowerCaseQuotedIdentifiers() {
+        return false
+    }
+
+    @Override
+    boolean storesMixedCaseQuotedIdentifiers() {
+        return false
+    }
+
+    @Override
+    String getIdentifierQuoteString() {
+        return null
+    }
+
+    @Override
+    String getSQLKeywords() {
+        return '' // do not return null here due to liquibase.database.jvm.JdbcConnection:30 to avoid NPE's there
+    }
+
+    @Override
+    String getNumericFunctions() {
+        return null
+    }
+
+    @Override
+    String getStringFunctions() {
+        return null
+    }
+
+    @Override
+    String getSystemFunctions() {
+        return null
+    }
+
+    @Override
+    String getTimeDateFunctions() {
+        return null
+    }
+
+    @Override
+    String getSearchStringEscape() {
+        return null
+    }
+
+    @Override
+    String getExtraNameCharacters() {
+        return null
+    }
+
+    @Override
+    boolean supportsAlterTableWithAddColumn() {
+        return false
+    }
+
+    @Override
+    boolean supportsAlterTableWithDropColumn() {
+        return false
+    }
+
+    @Override
+    boolean supportsColumnAliasing() {
+        return false
+    }
+
+    @Override
+    boolean nullPlusNonNullIsNull() {
+        return false
+    }
+
+    @Override
+    boolean supportsConvert() {
+        return false
+    }
+
+    @Override
+    boolean supportsConvert(int fromType, int toType) {
+        return false
+    }
+
+    @Override
+    boolean supportsTableCorrelationNames() {
+        return false
+    }
+
+    @Override
+    boolean supportsDifferentTableCorrelationNames() {
+        return false
+    }
+
+    @Override
+    boolean supportsExpressionsInOrderBy() {
+        return false
+    }
+
+    @Override
+    boolean supportsOrderByUnrelated() {
+        return false
+    }
+
+    @Override
+    boolean supportsGroupBy() {
+        return false
+    }
+
+    @Override
+    boolean supportsGroupByUnrelated() {
+        return false
+    }
+
+    @Override
+    boolean supportsGroupByBeyondSelect() {
+        return false
+    }
+
+    @Override
+    boolean supportsLikeEscapeClause() {
+        return false
+    }
+
+    @Override
+    boolean supportsMultipleResultSets() {
+        return false
+    }
+
+    @Override
+    boolean supportsMultipleTransactions() {
+        return false
+    }
+
+    @Override
+    boolean supportsNonNullableColumns() {
+        return false
+    }
+
+    @Override
+    boolean supportsMinimumSQLGrammar() {
+        return false
+    }
+
+    @Override
+    boolean supportsCoreSQLGrammar() {
+        return false
+    }
+
+    @Override
+    boolean supportsExtendedSQLGrammar() {
+        return false
+    }
+
+    @Override
+    boolean supportsANSI92EntryLevelSQL() {
+        return false
+    }
+
+    @Override
+    boolean supportsANSI92IntermediateSQL() {
+        return false
+    }
+
+    @Override
+    boolean supportsANSI92FullSQL() {
+        return false
+    }
+
+    @Override
+    boolean supportsIntegrityEnhancementFacility() {
+        return false
+    }
+
+    @Override
+    boolean supportsOuterJoins() {
+        return false
+    }
+
+    @Override
+    boolean supportsFullOuterJoins() {
+        return false
+    }
+
+    @Override
+    boolean supportsLimitedOuterJoins() {
+        return false
+    }
+
+    @Override
+    String getSchemaTerm() {
+        return null
+    }
+
+    @Override
+    String getProcedureTerm() {
+        return null
+    }
+
+    @Override
+    String getCatalogTerm() {
+        return null
+    }
+
+    @Override
+    boolean isCatalogAtStart() {
+        return false
+    }
+
+    @Override
+    String getCatalogSeparator() {
+        return null
+    }
+
+    @Override
+    boolean supportsSchemasInDataManipulation() {
+        return false
+    }
+
+    @Override
+    boolean supportsSchemasInProcedureCalls() {
+        return false
+    }
+
+    @Override
+    boolean supportsSchemasInTableDefinitions() {
+        return false
+    }
+
+    @Override
+    boolean supportsSchemasInIndexDefinitions() {
+        return false
+    }
+
+    @Override
+    boolean supportsSchemasInPrivilegeDefinitions() {
+        return false
+    }
+
+    @Override
+    boolean supportsCatalogsInDataManipulation() {
+        return false
+    }
+
+    @Override
+    boolean supportsCatalogsInProcedureCalls() {
+        return false
+    }
+
+    @Override
+    boolean supportsCatalogsInTableDefinitions() {
+        return false
+    }
+
+    @Override
+    boolean supportsCatalogsInIndexDefinitions() {
+        return false
+    }
+
+    @Override
+    boolean supportsCatalogsInPrivilegeDefinitions() {
+        return false
+    }
+
+    @Override
+    boolean supportsPositionedDelete() {
+        return false
+    }
+
+    @Override
+    boolean supportsPositionedUpdate() {
+        return false
+    }
+
+    @Override
+    boolean supportsSelectForUpdate() {
+        return false
+    }
+
+    @Override
+    boolean supportsStoredProcedures() {
+        return false
+    }
+
+    @Override
+    boolean supportsSubqueriesInComparisons() {
+        return false
+    }
+
+    @Override
+    boolean supportsSubqueriesInExists() {
+        return false
+    }
+
+    @Override
+    boolean supportsSubqueriesInIns() {
+        return false
+    }
+
+    @Override
+    boolean supportsSubqueriesInQuantifieds() {
+        return false
+    }
+
+    @Override
+    boolean supportsCorrelatedSubqueries() {
+        return false
+    }
+
+    @Override
+    boolean supportsUnion() {
+        return false
+    }
+
+    @Override
+    boolean supportsUnionAll() {
+        return false
+    }
+
+    @Override
+    boolean supportsOpenCursorsAcrossCommit() {
+        return false
+    }
+
+    @Override
+    boolean supportsOpenCursorsAcrossRollback() {
+        return false
+    }
+
+    @Override
+    boolean supportsOpenStatementsAcrossCommit() {
+        return false
+    }
+
+    @Override
+    boolean supportsOpenStatementsAcrossRollback() {
+        return false
+    }
+
+    @Override
+    int getMaxBinaryLiteralLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxCharLiteralLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxColumnNameLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxColumnsInGroupBy() {
+        return 0
+    }
+
+    @Override
+    int getMaxColumnsInIndex() {
+        return 0
+    }
+
+    @Override
+    int getMaxColumnsInOrderBy() {
+        return 0
+    }
+
+    @Override
+    int getMaxColumnsInSelect() {
+        return 0
+    }
+
+    @Override
+    int getMaxColumnsInTable() {
+        return 0
+    }
+
+    @Override
+    int getMaxConnections() {
+        return 0
+    }
+
+    @Override
+    int getMaxCursorNameLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxIndexLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxSchemaNameLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxProcedureNameLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxCatalogNameLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxRowSize() {
+        return 0
+    }
+
+    @Override
+    boolean doesMaxRowSizeIncludeBlobs() {
+        return false
+    }
+
+    @Override
+    int getMaxStatementLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxStatements() {
+        return 0
+    }
+
+    @Override
+    int getMaxTableNameLength() {
+        return 0
+    }
+
+    @Override
+    int getMaxTablesInSelect() {
+        return 0
+    }
+
+    @Override
+    int getMaxUserNameLength() {
+        return 0
+    }
+
+    @Override
+    int getDefaultTransactionIsolation() {
+        return 0
+    }
+
+    @Override
+    boolean supportsTransactions() {
+        return false
+    }
+
+    @Override
+    boolean supportsTransactionIsolationLevel(int level) {
+        return false
+    }
+
+    @Override
+    boolean supportsDataDefinitionAndDataManipulationTransactions() {
+        return false
+    }
+
+    @Override
+    boolean supportsDataManipulationTransactionsOnly() {
+        return false
+    }
+
+    @Override
+    boolean dataDefinitionCausesTransactionCommit() {
+        return false
+    }
+
+    @Override
+    boolean dataDefinitionIgnoredInTransactions() {
+        return false
+    }
+
+    @Override
+    ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getProcedureColumns(
+            String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) {
+        return null
+    }
+
+    @Override
+    ResultSet getSchemas() {
+        return null
+    }
+
+    @Override
+    ResultSet getCatalogs() {
+        return null
+    }
+
+    @Override
+    ResultSet getTableTypes() {
+        return null
+    }
+
+    @Override
+    ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) {
+        return null
+    }
+
+    @Override
+    ResultSet getVersionColumns(String catalog, String schema, String table) {
+        return null
+    }
+
+    @Override
+    ResultSet getPrimaryKeys(String catalog, String schema, String table) {
+        return null
+    }
+
+    @Override
+    ResultSet getImportedKeys(String catalog, String schema, String table) {
+        return null
+    }
+
+    @Override
+    ResultSet getExportedKeys(String catalog, String schema, String table) {
+        return null
+    }
+
+    @Override
+    ResultSet getCrossReference(
+            String parentCatalog,
+            String parentSchema,
+            String parentTable,
+            String foreignCatalog,
+            String foreignSchema,
+            String foreignTable) {
+        return null
+    }
+
+    @Override
+    ResultSet getTypeInfo() {
+        return null
+    }
+
+    @Override
+    ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) {
+        return null
+    }
+
+    @Override
+    boolean supportsResultSetType(int type) {
+        return false
+    }
+
+    @Override
+    boolean supportsResultSetConcurrency(int type, int concurrency) {
+        return false
+    }
+
+    @Override
+    boolean ownUpdatesAreVisible(int type) {
+        return false
+    }
+
+    @Override
+    boolean ownDeletesAreVisible(int type) {
+        return false
+    }
+
+    @Override
+    boolean ownInsertsAreVisible(int type) {
+        return false
+    }
+
+    @Override
+    boolean othersUpdatesAreVisible(int type) {
+        return false
+    }
+
+    @Override
+    boolean othersDeletesAreVisible(int type) {
+        return false
+    }
+
+    @Override
+    boolean othersInsertsAreVisible(int type) {
+        return false
+    }
+
+    @Override
+    boolean updatesAreDetected(int type) {
+        return false
+    }
+
+    @Override
+    boolean deletesAreDetected(int type) {
+        return false
+    }
+
+    @Override
+    boolean insertsAreDetected(int type) {
+        return false
+    }
+
+    @Override
+    boolean supportsBatchUpdates() {
+        return false
+    }
+
+    @Override
+    ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) {
+        return null
+    }
+
+    @Override
+    Connection getConnection() {
+        return null
+    }
+
+    @Override
+    boolean supportsSavepoints() {
+        return false
+    }
+
+    @Override
+    boolean supportsNamedParameters() {
+        return false
+    }
+
+    @Override
+    boolean supportsMultipleOpenResults() {
+        return false
+    }
+
+    @Override
+    boolean supportsGetGeneratedKeys() {
+        return false
+    }
+
+    @Override
+    ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getAttributes(
+            String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) {
+        return null
+    }
+
+    @Override
+    boolean supportsResultSetHoldability(int holdability) {
+        return false
+    }
+
+    @Override
+    int getResultSetHoldability() {
+        return 0
+    }
+
+    @Override
+    int getDatabaseMajorVersion() {
+        return 0
+    }
+
+    @Override
+    int getDatabaseMinorVersion() {
+        return 0
+    }
+
+    @Override
+    int getJDBCMajorVersion() {
+        return 0
+    }
+
+    @Override
+    int getJDBCMinorVersion() {
+        return 0
+    }
+
+    @Override
+    int getSQLStateType() {
+        return DatabaseMetaData.sqlStateSQL
+    }
+
+    @Override
+    boolean locatorsUpdateCopy() {
+        return false
+    }
+
+    @Override
+    boolean supportsStatementPooling() {
+        return false
+    }
+
+    @Override
+    RowIdLifetime getRowIdLifetime() {
+        return null
+    }
+
+    @Override
+    ResultSet getSchemas(String catalog, String schemaPattern) {
+        return null
+    }
+
+    @Override
+    boolean supportsStoredFunctionsUsingCallSyntax() {
+        return false
+    }
+
+    @Override
+    boolean autoCommitFailureClosesAllResultSets() {
+        return false
+    }
+
+    @Override
+    ResultSet getClientInfoProperties() {
+        return null
+    }
+
+    @Override
+    ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) {
+        return null
+    }
+
+    @Override
+    ResultSet getFunctionColumns(
+            String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) {
+        return null
+    }
+
+    @Override
+    def <T> T unwrap(Class<T> iface) {
+        return null
+    }
+
+    @Override
+    boolean isWrapperFor(Class<?> iface) {
+        return false
+    }
+
+    @Override
+    boolean generatedKeyAlwaysReturned() {
+        return false
+    }
+
+    @Override
+    ResultSet getPseudoColumns(String arg0, String arg1, String arg2, String arg3) {
+        return null
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java
deleted file mode 100644
index 6e351d9bfc..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java
+++ /dev/null
@@ -1,910 +0,0 @@
-package liquibase.ext.hibernate.database.connection;
-
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.RowIdLifetime;
-
-import org.hibernate.Version;
-
-/**
- * Implements the standard java.sql.DatabaseMetaData interface to allow the Hibernate integration to better fit into
- * what Liquibase expects.
- */
-public class HibernateConnectionMetadata implements DatabaseMetaData {
-
-    private final String url;
-
-    public HibernateConnectionMetadata(String url) {
-        this.url = url;
-    }
-
-    @Override
-    public boolean allProceduresAreCallable() {
-        return false;
-    }
-
-    @Override
-    public boolean allTablesAreSelectable() {
-        return false;
-    }
-
-    @Override
-    public String getURL() {
-        return url;
-    }
-
-    @Override
-    public String getUserName() {
-        return null;
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override
-    public boolean nullsAreSortedHigh() {
-        return false;
-    }
-
-    @Override
-    public boolean nullsAreSortedLow() {
-        return false;
-    }
-
-    @Override
-    public boolean nullsAreSortedAtStart() {
-        return false;
-    }
-
-    @Override
-    public boolean nullsAreSortedAtEnd() {
-        return false;
-    }
-
-    @Override
-    public String getDatabaseProductName() {
-        return "Hibernate";
-    }
-
-    @Override
-    public String getDatabaseProductVersion() {
-        return Version.getVersionString();
-    }
-
-    @Override
-    public String getDriverName() {
-        return null;
-    }
-
-    @Override
-    public String getDriverVersion() {
-        return "0";
-    }
-
-    @Override
-    public int getDriverMajorVersion() {
-        return 0;
-    }
-
-    @Override
-    public int getDriverMinorVersion() {
-        return 0;
-    }
-
-    @Override
-    public boolean usesLocalFiles() {
-        return false;
-    }
-
-    @Override
-    public boolean usesLocalFilePerTable() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsMixedCaseIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public boolean storesUpperCaseIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public boolean storesLowerCaseIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public boolean storesMixedCaseIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsMixedCaseQuotedIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public boolean storesUpperCaseQuotedIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public boolean storesLowerCaseQuotedIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public boolean storesMixedCaseQuotedIdentifiers() {
-        return false;
-    }
-
-    @Override
-    public String getIdentifierQuoteString() {
-        return null;
-    }
-
-    @Override
-    public String getSQLKeywords() {
-        return ""; // do not return null here due to liquibase.database.jvm.JdbcConnection:30 to avoid NPE's there
-    }
-
-    @Override
-    public String getNumericFunctions() {
-        return null;
-    }
-
-    @Override
-    public String getStringFunctions() {
-        return null;
-    }
-
-    @Override
-    public String getSystemFunctions() {
-        return null;
-    }
-
-    @Override
-    public String getTimeDateFunctions() {
-        return null;
-    }
-
-    @Override
-    public String getSearchStringEscape() {
-        return null;
-    }
-
-    @Override
-    public String getExtraNameCharacters() {
-        return null;
-    }
-
-    @Override
-    public boolean supportsAlterTableWithAddColumn() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsAlterTableWithDropColumn() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsColumnAliasing() {
-        return false;
-    }
-
-    @Override
-    public boolean nullPlusNonNullIsNull() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsConvert() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsConvert(int fromType, int toType) {
-        return false;
-    }
-
-    @Override
-    public boolean supportsTableCorrelationNames() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsDifferentTableCorrelationNames() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsExpressionsInOrderBy() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsOrderByUnrelated() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsGroupBy() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsGroupByUnrelated() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsGroupByBeyondSelect() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsLikeEscapeClause() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsMultipleResultSets() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsMultipleTransactions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsNonNullableColumns() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsMinimumSQLGrammar() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsCoreSQLGrammar() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsExtendedSQLGrammar() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsANSI92EntryLevelSQL() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsANSI92IntermediateSQL() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsANSI92FullSQL() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsIntegrityEnhancementFacility() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsOuterJoins() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsFullOuterJoins() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsLimitedOuterJoins() {
-        return false;
-    }
-
-    @Override
-    public String getSchemaTerm() {
-        return null;
-    }
-
-    @Override
-    public String getProcedureTerm() {
-        return null;
-    }
-
-    @Override
-    public String getCatalogTerm() {
-        return null;
-    }
-
-    @Override
-    public boolean isCatalogAtStart() {
-        return false;
-    }
-
-    @Override
-    public String getCatalogSeparator() {
-        return null;
-    }
-
-    @Override
-    public boolean supportsSchemasInDataManipulation() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSchemasInProcedureCalls() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSchemasInTableDefinitions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSchemasInIndexDefinitions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSchemasInPrivilegeDefinitions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsCatalogsInDataManipulation() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsCatalogsInProcedureCalls() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsCatalogsInTableDefinitions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsCatalogsInIndexDefinitions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsCatalogsInPrivilegeDefinitions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsPositionedDelete() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsPositionedUpdate() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSelectForUpdate() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsStoredProcedures() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSubqueriesInComparisons() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSubqueriesInExists() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSubqueriesInIns() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsSubqueriesInQuantifieds() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsCorrelatedSubqueries() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsUnion() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsUnionAll() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsOpenCursorsAcrossCommit() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsOpenCursorsAcrossRollback() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsOpenStatementsAcrossCommit() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsOpenStatementsAcrossRollback() {
-        return false;
-    }
-
-    @Override
-    public int getMaxBinaryLiteralLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxCharLiteralLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxColumnNameLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxColumnsInGroupBy() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxColumnsInIndex() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxColumnsInOrderBy() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxColumnsInSelect() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxColumnsInTable() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxConnections() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxCursorNameLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxIndexLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxSchemaNameLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxProcedureNameLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxCatalogNameLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxRowSize() {
-        return 0;
-    }
-
-    @Override
-    public boolean doesMaxRowSizeIncludeBlobs() {
-        return false;
-    }
-
-    @Override
-    public int getMaxStatementLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxStatements() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxTableNameLength() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxTablesInSelect() {
-        return 0;
-    }
-
-    @Override
-    public int getMaxUserNameLength() {
-        return 0;
-    }
-
-    @Override
-    public int getDefaultTransactionIsolation() {
-        return 0;
-    }
-
-    @Override
-    public boolean supportsTransactions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsTransactionIsolationLevel(int level) {
-        return false;
-    }
-
-    @Override
-    public boolean supportsDataDefinitionAndDataManipulationTransactions() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsDataManipulationTransactionsOnly() {
-        return false;
-    }
-
-    @Override
-    public boolean dataDefinitionCausesTransactionCommit() {
-        return false;
-    }
-
-    @Override
-    public boolean dataDefinitionIgnoredInTransactions() {
-        return false;
-    }
-
-    @Override
-    public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getProcedureColumns(
-            String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getSchemas() {
-        return null;
-    }
-
-    @Override
-    public ResultSet getCatalogs() {
-        return null;
-    }
-
-    @Override
-    public ResultSet getTableTypes() {
-        return null;
-    }
-
-    @Override
-    public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getVersionColumns(String catalog, String schema, String table) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getPrimaryKeys(String catalog, String schema, String table) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getImportedKeys(String catalog, String schema, String table) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getExportedKeys(String catalog, String schema, String table) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getCrossReference(
-            String parentCatalog,
-            String parentSchema,
-            String parentTable,
-            String foreignCatalog,
-            String foreignSchema,
-            String foreignTable) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getTypeInfo() {
-        return null;
-    }
-
-    @Override
-    public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) {
-        return null;
-    }
-
-    @Override
-    public boolean supportsResultSetType(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean supportsResultSetConcurrency(int type, int concurrency) {
-        return false;
-    }
-
-    @Override
-    public boolean ownUpdatesAreVisible(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean ownDeletesAreVisible(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean ownInsertsAreVisible(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean othersUpdatesAreVisible(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean othersDeletesAreVisible(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean othersInsertsAreVisible(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean updatesAreDetected(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean deletesAreDetected(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean insertsAreDetected(int type) {
-        return false;
-    }
-
-    @Override
-    public boolean supportsBatchUpdates() {
-        return false;
-    }
-
-    @Override
-    public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) {
-        return null;
-    }
-
-    @Override
-    public Connection getConnection() {
-        return null;
-    }
-
-    @Override
-    public boolean supportsSavepoints() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsNamedParameters() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsMultipleOpenResults() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsGetGeneratedKeys() {
-        return false;
-    }
-
-    @Override
-    public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getAttributes(
-            String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) {
-        return null;
-    }
-
-    @Override
-    public boolean supportsResultSetHoldability(int holdability) {
-        return false;
-    }
-
-    @Override
-    public int getResultSetHoldability() {
-        return 0;
-    }
-
-    @Override
-    public int getDatabaseMajorVersion() {
-        return 0;
-    }
-
-    @Override
-    public int getDatabaseMinorVersion() {
-        return 0;
-    }
-
-    @Override
-    public int getJDBCMajorVersion() {
-        return 0;
-    }
-
-    @Override
-    public int getJDBCMinorVersion() {
-        return 0;
-    }
-
-    @Override
-    public int getSQLStateType() {
-        return DatabaseMetaData.sqlStateSQL;
-    }
-
-    @Override
-    public boolean locatorsUpdateCopy() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsStatementPooling() {
-        return false;
-    }
-
-    @Override
-    public RowIdLifetime getRowIdLifetime() {
-        return null;
-    }
-
-    @Override
-    public ResultSet getSchemas(String catalog, String schemaPattern) {
-        return null;
-    }
-
-    @Override
-    public boolean supportsStoredFunctionsUsingCallSyntax() {
-        return false;
-    }
-
-    @Override
-    public boolean autoCommitFailureClosesAllResultSets() {
-        return false;
-    }
-
-    @Override
-    public ResultSet getClientInfoProperties() {
-        return null;
-    }
-
-    @Override
-    public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) {
-        return null;
-    }
-
-    @Override
-    public ResultSet getFunctionColumns(
-            String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) {
-        return null;
-    }
-
-    @Override
-    public <T> T unwrap(Class<T> iface) {
-        return null;
-    }
-
-    @Override
-    public boolean isWrapperFor(Class<?> iface) {
-        return false;
-    }
-
-    @Override
-    public boolean generatedKeyAlwaysReturned() {
-        return false;
-    }
-
-    @Override
-    public ResultSet getPseudoColumns(String arg0, String arg1, String arg2, String arg3) {
-        return null;
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.groovy
new file mode 100644
index 0000000000..13920250e9
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.groovy
@@ -0,0 +1,62 @@
+package liquibase.ext.hibernate.database.connection
+
+import java.sql.Connection
+import java.sql.Driver
+import java.sql.DriverPropertyInfo
+import java.sql.SQLFeatureNotSupportedException
+import java.util.logging.Logger
+
+import groovy.transform.CompileStatic
+import liquibase.database.LiquibaseExtDriver
+import liquibase.resource.ResourceAccessor
+
+/**
+ * Implements the standard java.sql.Driver interface to allow the Hibernate integration to better fit into
+ * what Liquibase expects.
+ */
+@CompileStatic
+class HibernateDriver implements Driver, LiquibaseExtDriver {
+
+    private ResourceAccessor resourceAccessor
+
+    @Override
+    Connection connect(String url, Properties info) {
+        return new HibernateConnection(url, resourceAccessor)
+    }
+
+    @Override
+    boolean acceptsURL(String url) {
+        return url.startsWith('hibernate:')
+    }
+
+    @Override
+    DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
+        return new DriverPropertyInfo[0]
+    }
+
+    @Override
+    int getMajorVersion() {
+        return 0
+    }
+
+    @Override
+    int getMinorVersion() {
+        return 0
+    }
+
+    @Override
+    boolean jdbcCompliant() {
+        return false
+    }
+
+    @Override
+    Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException()
+    }
+
+    @Override
+    void setResourceAccessor(ResourceAccessor accessor) {
+        this.resourceAccessor = accessor
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.java
deleted file mode 100644
index bc7ba5952c..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package liquibase.ext.hibernate.database.connection;
-
-import java.sql.Connection;
-import java.sql.Driver;
-import java.sql.DriverPropertyInfo;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.Properties;
-import java.util.logging.Logger;
-
-import liquibase.database.LiquibaseExtDriver;
-import liquibase.resource.ResourceAccessor;
-
-/**
- * Implements the standard java.sql.Driver interface to allow the Hibernate integration to better fit into
- * what Liquibase expects.
- */
-public class HibernateDriver implements Driver, LiquibaseExtDriver {
-
-    private ResourceAccessor resourceAccessor;
-
-    @Override
-    public Connection connect(String url, Properties info) {
-        return new HibernateConnection(url, resourceAccessor);
-    }
-
-    @Override
-    public boolean acceptsURL(String url) {
-        return url.startsWith("hibernate:");
-    }
-
-    @Override
-    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
-        return new DriverPropertyInfo[0];
-    }
-
-    @Override
-    public int getMajorVersion() {
-        return 0;
-    }
-
-    @Override
-    public int getMinorVersion() {
-        return 0;
-    }
-
-    @Override
-    public boolean jdbcCompliant() {
-        return false;
-    }
-
-    @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    @Override
-    public void setResourceAccessor(ResourceAccessor accessor) {
-        this.resourceAccessor = accessor;
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.groovy
new file mode 100644
index 0000000000..360f828da2
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.groovy
@@ -0,0 +1,107 @@
+package liquibase.ext.hibernate.diff
+
+import groovy.transform.CompileStatic
+import liquibase.change.Change
+import liquibase.database.Database
+import liquibase.diff.Difference
+import liquibase.diff.ObjectDifferences
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.core.ChangedColumnChangeGenerator
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.statement.DatabaseFunction
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Column
+import liquibase.structure.core.DataType
+
+/**
+ * Hibernate and database types tend to look different even though they are not.
+ * The only change that we are handling it size change, and even for this one there are exceptions.
+ */
+@CompileStatic
+class HibernateChangedColumnChangeGenerator extends ChangedColumnChangeGenerator {
+
+    private static final List<String> TYPES_TO_IGNORE_SIZE = List.of('TIMESTAMP', 'TIME')
+
+    @Override
+    int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        return Column.isAssignableFrom(objectType) ? PRIORITY_ADDITIONAL : PRIORITY_NONE
+    }
+
+    @Override
+    protected void handleTypeDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) {
+        if (isHibernateRelated(referenceDatabase, comparisonDatabase)) {
+            handleHibernateTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase)
+        } else {
+            super.handleTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase)
+        }
+    }
+
+    private void handleHibernateTypeDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database refDb, Database compDb) {
+        if (shouldIgnoreSize(column)) {
+            return
+        }
+
+        if (differences.getDifference('type') != null) {
+            filterIrrelevantDifferences(differences)
+            super.handleTypeDifferences(column, differences, control, changes, refDb, compDb)
+        }
+    }
+
+    private void filterIrrelevantDifferences(ObjectDifferences differences) {
+        for (Difference diff : new ArrayList<Difference>(differences.getDifferences())) {
+            if (!isMeaningfulDifference(diff)) {
+                differences.removeDifference(diff.getField())
+            }
+        }
+    }
+
+    private boolean isMeaningfulDifference(Difference diff) {
+        Object referenceValue = diff.getReferenceValue()
+        Object comparedValue = diff.getComparedValue()
+        return referenceValue instanceof DataType &&
+               comparedValue instanceof DataType &&
+               !isSizeEqualOrNull(((DataType) referenceValue).getColumnSize(), ((DataType) comparedValue).getColumnSize())
+    }
+
+    @Override
+    protected void handleDefaultValueDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) {
+        if (!isHibernateRelated(referenceDatabase, comparisonDatabase)) {
+            super.handleDefaultValueDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase)
+            return
+        }
+
+        if (isFunctionDefaultAddingToNull(differences)) {
+            return
+        }
+
+        if (differences.getDifference('defaultValue') != null) {
+            super.handleDefaultValueDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase)
+        }
+    }
+
+    private boolean shouldIgnoreSize(Column column) {
+        String typeName = column.getType().getTypeName()
+        for (String type : TYPES_TO_IGNORE_SIZE) {
+            if (type.equalsIgnoreCase(typeName)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    private boolean isSizeEqualOrNull(Integer s1, Integer s2) {
+        return s1 == null || s2 == null || s1.equals(s2)
+    }
+
+    private boolean isFunctionDefaultAddingToNull(ObjectDifferences differences) {
+        Difference difference = differences.getDifference('defaultValue')
+        return difference != null &&
+                difference.getReferenceValue() == null &&
+                difference.getComparedValue() instanceof DatabaseFunction
+    }
+
+    private boolean isHibernateRelated(Database d1, Database d2) {
+        return d1 instanceof HibernateDatabase || d2 instanceof HibernateDatabase
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java
deleted file mode 100644
index ec923545bc..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package liquibase.ext.hibernate.diff;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Predicate;
-
-import liquibase.change.Change;
-import liquibase.database.Database;
-import liquibase.diff.Difference;
-import liquibase.diff.ObjectDifferences;
-import liquibase.diff.output.DiffOutputControl;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.statement.DatabaseFunction;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Column;
-import liquibase.structure.core.DataType;
-
-/**
- * Hibernate and database types tend to look different even though they are not.
- * The only change that we are handling it size change, and even for this one there are exceptions.
- */
-public class HibernateChangedColumnChangeGenerator extends liquibase.diff.output.changelog.core.ChangedColumnChangeGenerator {
-
-    private static final List<String> TYPES_TO_IGNORE_SIZE = List.of("TIMESTAMP", "TIME");
-
-    @Override
-    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        return Column.class.isAssignableFrom(objectType) ? PRIORITY_ADDITIONAL : PRIORITY_NONE;
-    }
-
-    @Override
-    protected void handleTypeDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) {
-        if (isHibernateRelated(referenceDatabase, comparisonDatabase)) {
-            handleHibernateTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase);
-        } else {
-            super.handleTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase);
-        }
-    }
-
-    private void handleHibernateTypeDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database refDb, Database compDb) {
-        if (shouldIgnoreSize(column)) return;
-
-        Optional.ofNullable(differences.getDifference("type")).ifPresent(diff -> {
-            filterIrrelevantDifferences(differences);
-            super.handleTypeDifferences(column, differences, control, changes, refDb, compDb);
-        });
-    }
-
-    private void filterIrrelevantDifferences(ObjectDifferences differences) {
-        new ArrayList<>(differences.getDifferences()).stream()
-                .filter(Predicate.not(this::isMeaningfulDifference))
-                .forEach(diff -> differences.removeDifference(diff.getField()));
-    }
-
-    private boolean isMeaningfulDifference(Difference diff) {
-        return diff.getReferenceValue() instanceof DataType refType && 
-               diff.getComparedValue() instanceof DataType compType &&
-               !isSizeEqualOrNull(refType.getColumnSize(), compType.getColumnSize());
-    }
-
-    @Override
-    protected void handleDefaultValueDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) {
-        if (!isHibernateRelated(referenceDatabase, comparisonDatabase)) {
-            super.handleDefaultValueDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase);
-            return;
-        }
-
-        if (isFunctionDefaultAddingToNull(differences)) return;
-
-        Optional.ofNullable(differences.getDifference("defaultValue"))
-                .ifPresent(d -> super.handleDefaultValueDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase));
-    }
-
-    private boolean shouldIgnoreSize(Column column) {
-        return TYPES_TO_IGNORE_SIZE.stream().anyMatch(type -> type.equalsIgnoreCase(column.getType().getTypeName()));
-    }
-
-    private boolean isSizeEqualOrNull(Integer s1, Integer s2) {
-        return s1 == null || s2 == null || s1.equals(s2);
-    }
-
-    private boolean isFunctionDefaultAddingToNull(ObjectDifferences differences) {
-        return Optional.ofNullable(differences.getDifference("defaultValue"))
-                .filter(d -> d.getReferenceValue() == null && d.getComparedValue() instanceof DatabaseFunction)
-                .isPresent();
-    }
-
-    private boolean isHibernateRelated(Database d1, Database d2) {
-        return d1 instanceof HibernateDatabase || d2 instanceof HibernateDatabase;
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.groovy
new file mode 100644
index 0000000000..26977acdfd
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.groovy
@@ -0,0 +1,49 @@
+package liquibase.ext.hibernate.diff
+
+import groovy.transform.CompileStatic
+import liquibase.change.Change
+import liquibase.database.Database
+import liquibase.diff.ObjectDifferences
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.ChangeGeneratorChain
+import liquibase.diff.output.changelog.core.ChangedForeignKeyChangeGenerator
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.ForeignKey
+
+/**
+ * Hibernate doesn't know about all the variations that occur with foreign keys but just whether the FK exists or not.
+ * To prevent changing customized foreign keys, we suppress all foreign key changes from hibernate.
+ */
+@CompileStatic
+class HibernateChangedForeignKeyChangeGenerator extends ChangedForeignKeyChangeGenerator {
+
+    @Override
+    int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        if (ForeignKey.isAssignableFrom(objectType)) {
+            return PRIORITY_ADDITIONAL
+        }
+        return PRIORITY_NONE
+    }
+
+    @Override
+    Change[] fixChanged(
+            DatabaseObject changedObject,
+            ObjectDifferences differences,
+            DiffOutputControl control,
+            Database referenceDatabase,
+            Database comparisonDatabase,
+            ChangeGeneratorChain chain) {
+        if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) {
+            differences.removeDifference('deleteRule')
+            differences.removeDifference('updateRule')
+            differences.removeDifference('validate')
+            if (!differences.hasDifferences()) {
+                return new Change[0]
+            }
+        }
+
+        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain)
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java
deleted file mode 100644
index b6711e9b80..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package liquibase.ext.hibernate.diff;
-
-import liquibase.change.Change;
-import liquibase.database.Database;
-import liquibase.diff.ObjectDifferences;
-import liquibase.diff.output.DiffOutputControl;
-import liquibase.diff.output.changelog.ChangeGeneratorChain;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.ForeignKey;
-
-/**
- * Hibernate doesn't know about all the variations that occur with foreign keys but just whether the FK exists or not.
- * To prevent changing customized foreign keys, we suppress all foreign key changes from hibernate.
- */
-public class HibernateChangedForeignKeyChangeGenerator
-        extends liquibase.diff.output.changelog.core.ChangedForeignKeyChangeGenerator {
-
-    @Override
-    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        if (ForeignKey.class.isAssignableFrom(objectType)) {
-            return PRIORITY_ADDITIONAL;
-        }
-        return PRIORITY_NONE;
-    }
-
-    @Override
-    public Change[] fixChanged(
-            DatabaseObject changedObject,
-            ObjectDifferences differences,
-            DiffOutputControl control,
-            Database referenceDatabase,
-            Database comparisonDatabase,
-            ChangeGeneratorChain chain) {
-        if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) {
-            differences.removeDifference("deleteRule");
-            differences.removeDifference("updateRule");
-            differences.removeDifference("validate");
-            if (!differences.hasDifferences()) {
-                return new Change[0];
-            }
-        }
-
-        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain);
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.groovy
new file mode 100644
index 0000000000..96c35ad93e
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.groovy
@@ -0,0 +1,48 @@
+package liquibase.ext.hibernate.diff
+
+import groovy.transform.CompileStatic
+import liquibase.change.Change
+import liquibase.database.Database
+import liquibase.diff.ObjectDifferences
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.ChangeGeneratorChain
+import liquibase.diff.output.changelog.core.ChangedPrimaryKeyChangeGenerator
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.PrimaryKey
+
+/**
+ * Hibernate doesn't know about all the variations that occur with primary keys, especially backing index stuff.
+ * To prevent changing customized primary keys, we suppress this kind of changes from hibernate side.
+ */
+@CompileStatic
+class HibernateChangedPrimaryKeyChangeGenerator extends ChangedPrimaryKeyChangeGenerator {
+
+    @Override
+    int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        if (PrimaryKey.isAssignableFrom(objectType)) {
+            return PRIORITY_ADDITIONAL
+        }
+        return PRIORITY_NONE
+    }
+
+    @Override
+    Change[] fixChanged(
+            DatabaseObject changedObject,
+            ObjectDifferences differences,
+            DiffOutputControl control,
+            Database referenceDatabase,
+            Database comparisonDatabase,
+            ChangeGeneratorChain chain) {
+        if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) {
+            differences.removeDifference('unique')
+            differences.removeDifference('validate')
+            if (!differences.hasDifferences()) {
+                return new Change[0]
+            }
+        }
+
+        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain)
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java
deleted file mode 100644
index 4e38497311..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package liquibase.ext.hibernate.diff;
-
-import liquibase.change.Change;
-import liquibase.database.Database;
-import liquibase.diff.ObjectDifferences;
-import liquibase.diff.output.DiffOutputControl;
-import liquibase.diff.output.changelog.ChangeGeneratorChain;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.PrimaryKey;
-
-/**
- * Hibernate doesn't know about all the variations that occur with primary keys, especially backing index stuff.
- * To prevent changing customized primary keys, we suppress this kind of changes from hibernate side.
- */
-public class HibernateChangedPrimaryKeyChangeGenerator
-        extends liquibase.diff.output.changelog.core.ChangedPrimaryKeyChangeGenerator {
-
-    @Override
-    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        if (PrimaryKey.class.isAssignableFrom(objectType)) {
-            return PRIORITY_ADDITIONAL;
-        }
-        return PRIORITY_NONE;
-    }
-
-    @Override
-    public Change[] fixChanged(
-            DatabaseObject changedObject,
-            ObjectDifferences differences,
-            DiffOutputControl control,
-            Database referenceDatabase,
-            Database comparisonDatabase,
-            ChangeGeneratorChain chain) {
-        if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) {
-            differences.removeDifference("unique");
-            differences.removeDifference("validate");
-            if (!differences.hasDifferences()) {
-                return new Change[0];
-            }
-        }
-
-        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain);
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.groovy
similarity index 58%
rename from grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.groovy
index 4ae4bba7fd..27fc322211 100644
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.groovy
@@ -16,97 +16,101 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package liquibase.ext.hibernate.diff;
-
-import java.util.Objects;
-import java.util.Set;
-
-import liquibase.change.Change;
-import liquibase.database.Database;
-import liquibase.diff.Difference;
-import liquibase.diff.ObjectDifferences;
-import liquibase.diff.output.DiffOutputControl;
-import liquibase.diff.output.changelog.ChangeGeneratorChain;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Sequence;
+package liquibase.ext.hibernate.diff
+
+import groovy.transform.CompileStatic
+import liquibase.change.Change
+import liquibase.database.Database
+import liquibase.diff.Difference
+import liquibase.diff.ObjectDifferences
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.ChangeGeneratorChain
+import liquibase.diff.output.changelog.core.ChangedSequenceChangeGenerator
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Sequence
 
 /**
  * Hibernate manages sequences only by the name, startValue and incrementBy fields.
  * However, non-hibernate databases might return default values for other fields triggering false positives.
  */
-public class HibernateChangedSequenceChangeGenerator
-        extends liquibase.diff.output.changelog.core.ChangedSequenceChangeGenerator {
+@CompileStatic
+class HibernateChangedSequenceChangeGenerator extends ChangedSequenceChangeGenerator {
 
-    private static final String FIELD_NAME = "name";
-    private static final String FIELD_START_VALUE = "startValue";
-    private static final String FIELD_INCREMENT_BY = "incrementBy";
+    private static final String FIELD_NAME = 'name'
+    private static final String FIELD_START_VALUE = 'startValue'
+    private static final String FIELD_INCREMENT_BY = 'incrementBy'
+
+    private static final Set<String> HIBERNATE_SEQUENCE_FIELDS = Set.of(FIELD_NAME, FIELD_START_VALUE, FIELD_INCREMENT_BY)
 
-    private static final Set<String> HIBERNATE_SEQUENCE_FIELDS = Set.of(FIELD_NAME, FIELD_START_VALUE, FIELD_INCREMENT_BY);
-    
     // Default values used by Hibernate's SequenceStyleGenerator
-    private static final String DEFAULT_INITIAL_VALUE = "1";
-    private static final String DEFAULT_INCREMENT_SIZE = "50";
-    private static final Set<String> DEFAULT_VALUES = Set.of(DEFAULT_INITIAL_VALUE, DEFAULT_INCREMENT_SIZE);
+    private static final String DEFAULT_INITIAL_VALUE = '1'
+    private static final String DEFAULT_INCREMENT_SIZE = '50'
+    private static final Set<String> DEFAULT_VALUES = Set.of(DEFAULT_INITIAL_VALUE, DEFAULT_INCREMENT_SIZE)
 
     @Override
-    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        return Sequence.class.isAssignableFrom(objectType) ? PRIORITY_ADDITIONAL : PRIORITY_NONE;
+    int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        return Sequence.isAssignableFrom(objectType) ? PRIORITY_ADDITIONAL : PRIORITY_NONE
     }
 
     @Override
-    public Change[] fixChanged(
+    Change[] fixChanged(
             DatabaseObject changedObject,
             ObjectDifferences differences,
             DiffOutputControl control,
             Database referenceDatabase,
             Database comparisonDatabase,
             ChangeGeneratorChain chain) {
-        
+
         if (isHibernateRelated(referenceDatabase, comparisonDatabase)) {
-            filterIrrelevantDifferences(differences, referenceDatabase, comparisonDatabase);
+            filterIrrelevantDifferences(differences, referenceDatabase, comparisonDatabase)
         }
 
-        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain);
+        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain)
     }
 
     private void filterIrrelevantDifferences(ObjectDifferences differences, Database refDb, Database compDb) {
-        differences.getDifferences().stream()
-                .filter(diff -> isIgnoredField(diff) || isAdvancedIgnoredDifference(diff, refDb, compDb))
-                .map(Difference::getField)
-                .toList()
-                .forEach(differences::removeDifference);
+        List<String> fieldsToRemove = new ArrayList<>()
+        for (Difference diff : differences.getDifferences()) {
+            if (isIgnoredField(diff) || isAdvancedIgnoredDifference(diff, refDb, compDb)) {
+                fieldsToRemove.add(diff.getField())
+            }
+        }
+        for (String field : fieldsToRemove) {
+            differences.removeDifference(field)
+        }
     }
 
     private boolean isIgnoredField(Difference diff) {
-        return !HIBERNATE_SEQUENCE_FIELDS.contains(diff.getField());
+        return !HIBERNATE_SEQUENCE_FIELDS.contains(diff.getField())
     }
 
     private boolean isAdvancedIgnoredDifference(Difference diff, Database refDb, Database compDb) {
-        String field = diff.getField();
-        String refValue = Objects.toString(diff.getReferenceValue(), null);
-        String compValue = Objects.toString(diff.getComparedValue(), null);
+        String field = diff.getField()
+        String refValue = Objects.toString(diff.getReferenceValue(), null)
+        String compValue = Objects.toString(diff.getComparedValue(), null)
 
         if (FIELD_NAME.equals(field)) {
-            return isCaseInsensitiveMatch(refValue, compValue, refDb, compDb);
+            return isCaseInsensitiveMatch(refValue, compValue, refDb, compDb)
         }
 
         if (FIELD_START_VALUE.equals(field) || FIELD_INCREMENT_BY.equals(field)) {
-            return isDefaultOrNullMatch(refValue, compValue);
+            return isDefaultOrNullMatch(refValue, compValue)
         }
 
-        return false;
+        return false
     }
 
     private boolean isCaseInsensitiveMatch(String v1, String v2, Database d1, Database d2) {
-        return (!d1.isCaseSensitive() || !d2.isCaseSensitive()) && v1 != null && v1.equalsIgnoreCase(v2);
+        return (!d1.isCaseSensitive() || !d2.isCaseSensitive()) && v1 != null && v1.equalsIgnoreCase(v2)
     }
 
     private boolean isDefaultOrNullMatch(String v1, String v2) {
-        return (v1 == null && DEFAULT_VALUES.contains(v2)) || (v2 == null && DEFAULT_VALUES.contains(v1));
+        return (v1 == null && DEFAULT_VALUES.contains(v2)) || (v2 == null && DEFAULT_VALUES.contains(v1))
     }
 
     private boolean isHibernateRelated(Database d1, Database d2) {
-        return d1 instanceof HibernateDatabase || d2 instanceof HibernateDatabase;
+        return d1 instanceof HibernateDatabase || d2 instanceof HibernateDatabase
     }
+
 }
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.groovy
similarity index 60%
rename from grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.groovy
index aae26b3268..3d07924e70 100644
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.groovy
@@ -16,35 +16,37 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package liquibase.ext.hibernate.diff;
+package liquibase.ext.hibernate.diff
 
-import liquibase.change.Change;
-import liquibase.database.Database;
-import liquibase.diff.ObjectDifferences;
-import liquibase.diff.output.DiffOutputControl;
-import liquibase.diff.output.changelog.ChangeGeneratorChain;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.UniqueConstraint;
+import groovy.transform.CompileStatic
+import liquibase.change.Change
+import liquibase.database.Database
+import liquibase.diff.ObjectDifferences
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.ChangeGeneratorChain
+import liquibase.diff.output.changelog.core.ChangedUniqueConstraintChangeGenerator
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.UniqueConstraint
 
 /**
  * Unique attribute for unique constraints backing index can have different values dependending on the database implementation,
  * so we suppress all unique constraint changes based on unique constraints.
  *
  */
-public class HibernateChangedUniqueConstraintChangeGenerator
-        extends liquibase.diff.output.changelog.core.ChangedUniqueConstraintChangeGenerator {
+@CompileStatic
+class HibernateChangedUniqueConstraintChangeGenerator extends ChangedUniqueConstraintChangeGenerator {
 
     @Override
-    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        if (UniqueConstraint.class.isAssignableFrom(objectType)) {
-            return PRIORITY_ADDITIONAL;
+    int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        if (UniqueConstraint.isAssignableFrom(objectType)) {
+            return PRIORITY_ADDITIONAL
         }
-        return PRIORITY_NONE;
+        return PRIORITY_NONE
     }
 
     @Override
-    public Change[] fixChanged(
+    Change[] fixChanged(
             DatabaseObject changedObject,
             ObjectDifferences differences,
             DiffOutputControl control,
@@ -52,11 +54,12 @@ public class HibernateChangedUniqueConstraintChangeGenerator
             Database comparisonDatabase,
             ChangeGeneratorChain chain) {
         if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) {
-            differences.removeDifference("unique");
+            differences.removeDifference('unique')
             if (!differences.hasDifferences()) {
-                return new Change[0];
+                return new Change[0]
             }
         }
-        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain);
+        return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain)
     }
+
 }
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.groovy
new file mode 100644
index 0000000000..914bd9df68
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.groovy
@@ -0,0 +1,40 @@
+package liquibase.ext.hibernate.diff
+
+import groovy.transform.CompileStatic
+import liquibase.change.Change
+import liquibase.database.Database
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.ChangeGeneratorChain
+import liquibase.diff.output.changelog.core.MissingSequenceChangeGenerator
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Sequence
+
+@CompileStatic
+class HibernateMissingSequenceChangeGenerator extends MissingSequenceChangeGenerator {
+
+    @Override
+    int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        if (Sequence.isAssignableFrom(objectType)) {
+            return PRIORITY_ADDITIONAL
+        }
+        return PRIORITY_NONE
+    }
+
+    @Override
+    Change[] fixMissing(
+            DatabaseObject missingObject,
+            DiffOutputControl control,
+            Database referenceDatabase,
+            Database comparisonDatabase,
+            ChangeGeneratorChain chain) {
+        if (referenceDatabase instanceof HibernateDatabase && !comparisonDatabase.supportsSequences()) {
+            return new Change[0]
+        } else if (comparisonDatabase instanceof HibernateDatabase && !referenceDatabase.supportsSequences()) {
+            return new Change[0]
+        } else {
+            return super.fixMissing(missingObject, control, referenceDatabase, comparisonDatabase, chain)
+        }
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java
deleted file mode 100644
index 742b225d95..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package liquibase.ext.hibernate.diff;
-
-import liquibase.change.Change;
-import liquibase.database.Database;
-import liquibase.diff.output.DiffOutputControl;
-import liquibase.diff.output.changelog.ChangeGeneratorChain;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Sequence;
-
-public class HibernateMissingSequenceChangeGenerator
-        extends liquibase.diff.output.changelog.core.MissingSequenceChangeGenerator {
-
-    @Override
-    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        if (Sequence.class.isAssignableFrom(objectType)) {
-            return PRIORITY_ADDITIONAL;
-        }
-        return PRIORITY_NONE;
-    }
-
-    @Override
-    public Change[] fixMissing(
-            DatabaseObject missingObject,
-            DiffOutputControl control,
-            Database referenceDatabase,
-            Database comparisonDatabase,
-            ChangeGeneratorChain chain) {
-        if (referenceDatabase instanceof HibernateDatabase && !comparisonDatabase.supportsSequences()) {
-            return new Change[0];
-        } else if (comparisonDatabase instanceof HibernateDatabase && !referenceDatabase.supportsSequences()) {
-            return new Change[0];
-        } else {
-            return super.fixMissing(missingObject, control, referenceDatabase, comparisonDatabase, chain);
-        }
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.groovy
new file mode 100644
index 0000000000..9365371956
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.groovy
@@ -0,0 +1,42 @@
+package liquibase.ext.hibernate.diff
+
+import groovy.transform.CompileStatic
+import liquibase.change.Change
+import liquibase.database.Database
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.ChangeGeneratorChain
+import liquibase.diff.output.changelog.core.UnexpectedIndexChangeGenerator
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Index
+
+/**
+ * Indexes tend to be added in the database that don't correspond to what is in Hibernate, so we suppress all dropIndex changes
+ * based on indexes defined in the database but not in hibernate.
+ */
+@CompileStatic
+class HibernateUnexpectedIndexChangeGenerator extends UnexpectedIndexChangeGenerator {
+
+    @Override
+    int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        if (Index.isAssignableFrom(objectType)) {
+            return PRIORITY_ADDITIONAL
+        }
+        return PRIORITY_NONE
+    }
+
+    @Override
+    Change[] fixUnexpected(
+            DatabaseObject unexpectedObject,
+            DiffOutputControl control,
+            Database referenceDatabase,
+            Database comparisonDatabase,
+            ChangeGeneratorChain chain) {
+        if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) {
+            return new Change[0]
+        } else {
+            return super.fixUnexpected(unexpectedObject, control, referenceDatabase, comparisonDatabase, chain)
+        }
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java
deleted file mode 100644
index 1c643b31f4..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package liquibase.ext.hibernate.diff;
-
-import liquibase.change.Change;
-import liquibase.database.Database;
-import liquibase.diff.output.DiffOutputControl;
-import liquibase.diff.output.changelog.ChangeGeneratorChain;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Index;
-
-/**
- * Indexes tend to be added in the database that don't correspond to what is in Hibernate, so we suppress all dropIndex changes
- * based on indexes defined in the database but not in hibernate.
- */
-public class HibernateUnexpectedIndexChangeGenerator
-        extends liquibase.diff.output.changelog.core.UnexpectedIndexChangeGenerator {
-
-    @Override
-    public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        if (Index.class.isAssignableFrom(objectType)) {
-            return PRIORITY_ADDITIONAL;
-        }
-        return PRIORITY_NONE;
-    }
-
-    @Override
-    public Change[] fixUnexpected(
-            DatabaseObject unexpectedObject,
-            DiffOutputControl control,
-            Database referenceDatabase,
-            Database comparisonDatabase,
-            ChangeGeneratorChain chain) {
-        if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) {
-            return new Change[0];
-        } else {
-            return super.fixUnexpected(unexpectedObject, control, referenceDatabase, comparisonDatabase, chain);
-        }
-    }
-}


(grails-core) 08/14: Convert grails-data-hibernate7-spring-orm batch 2 to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 932664df370febaa802377c4a172d1816e3d50bc
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 21:27:02 2026 -0500

    Convert grails-data-hibernate7-spring-orm batch 2 to Groovy
    
    Converts SessionFactoryUtils, SpringBeanContainer, LocalSessionFactoryBuilder
    and support.OpenSessionInViewInterceptor to @CompileStatic Groovy, preserving
    the upstream Spring copyright headers verbatim. SessionFactoryUtils keeps its
    public SESSION_SYNCHRONIZATION_ORDER constant and package-private flush();
    the background-bootstrap proxy handler uses an explicit Callable closure and a
    classic switch in place of the Java method reference and switch expression.
    
    Verified with codenarcMain/checkstyleMain on spring-orm and the full
    grails-data-hibernate7-core, grails-data-hibernate7 and
    grails-data-hibernate7-spring-boot test suites.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 ...lder.java => LocalSessionFactoryBuilder.groovy} | 383 ++++++++++-----------
 ...actoryUtils.java => SessionFactoryUtils.groovy} | 254 +++++++-------
 ...anContainer.java => SpringBeanContainer.groovy} | 221 ++++++------
 ...or.java => OpenSessionInViewInterceptor.groovy} | 153 ++++----
 4 files changed, 490 insertions(+), 521 deletions(-)

diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBuilder.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBuilder.groovy
similarity index 59%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBuilder.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBuilder.groovy
index c0cfe30908..231999618d 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBuilder.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBuilder.groovy
@@ -13,63 +13,55 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.grails.orm.hibernate.support.hibernate7;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-import javax.sql.DataSource;
-
-import jakarta.persistence.AttributeConverter;
-import jakarta.persistence.Converter;
-import jakarta.persistence.Embeddable;
-import jakarta.persistence.Entity;
-import jakarta.persistence.MappedSuperclass;
-import jakarta.transaction.TransactionManager;
-
-import org.hibernate.HibernateException;
-import org.hibernate.MappingException;
-import org.hibernate.SessionFactory;
-import org.hibernate.boot.MetadataSources;
-import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
-import org.hibernate.cache.spi.RegionFactory;
-import org.hibernate.cfg.AvailableSettings;
-import org.hibernate.cfg.Configuration;
-import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
-import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
-import org.hibernate.engine.spi.SessionFactoryImplementor;
-import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.core.InfrastructureProxy;
-import org.springframework.core.SpringProperties;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.ResourceLoader;
-import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
-import org.springframework.core.io.support.ResourcePatternResolver;
-import org.springframework.core.io.support.ResourcePatternUtils;
-import org.springframework.core.task.AsyncTaskExecutor;
-import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
-import org.springframework.core.type.classreading.ClassFormatException;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.AnnotationTypeFilter;
-import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.transaction.jta.JtaTransactionManager;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
+package org.grails.orm.hibernate.support.hibernate7
+
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+import java.util.concurrent.Callable
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+
+import javax.sql.DataSource
+
+import groovy.transform.CompileStatic
+import jakarta.persistence.AttributeConverter
+import jakarta.persistence.Converter
+import jakarta.persistence.Embeddable
+import jakarta.persistence.Entity
+import jakarta.persistence.MappedSuperclass
+import jakarta.transaction.TransactionManager
+import org.hibernate.HibernateException
+import org.hibernate.MappingException
+import org.hibernate.SessionFactory
+import org.hibernate.boot.MetadataSources
+import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder
+import org.hibernate.cache.spi.RegionFactory
+import org.hibernate.cfg.AvailableSettings
+import org.hibernate.cfg.Configuration
+import org.hibernate.context.spi.CurrentTenantIdentifierResolver
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider
+import org.hibernate.engine.spi.SessionFactoryImplementor
+import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
+import org.springframework.core.InfrastructureProxy
+import org.springframework.core.SpringProperties
+import org.springframework.core.io.Resource
+import org.springframework.core.io.ResourceLoader
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver
+import org.springframework.core.io.support.ResourcePatternResolver
+import org.springframework.core.io.support.ResourcePatternUtils
+import org.springframework.core.task.AsyncTaskExecutor
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory
+import org.springframework.core.type.classreading.ClassFormatException
+import org.springframework.core.type.classreading.MetadataReader
+import org.springframework.core.type.classreading.MetadataReaderFactory
+import org.springframework.core.type.filter.AnnotationTypeFilter
+import org.springframework.core.type.filter.TypeFilter
+import org.springframework.transaction.jta.JtaTransactionManager
+import org.springframework.util.Assert
+import org.springframework.util.ClassUtils
 
 /**
  * A Spring-provided extension of the standard Hibernate {@link Configuration} class,
@@ -99,36 +91,37 @@ import org.springframework.util.ClassUtils;
  * @see #LocalSessionFactoryBuilder(DataSource, ResourceLoader, MetadataSources)
  * @see BootstrapServiceRegistryBuilder
  */
-@SuppressWarnings("serial")
-public class LocalSessionFactoryBuilder extends Configuration {
+@CompileStatic
+@SuppressWarnings('serial')
+class LocalSessionFactoryBuilder extends Configuration {
 
-    private static final String RESOURCE_PATTERN = "/**/*.class";
+    private static final String RESOURCE_PATTERN = '/**/*.class'
 
-    private static final String PACKAGE_INFO_SUFFIX = ".package-info";
+    private static final String PACKAGE_INFO_SUFFIX = '.package-info'
 
-    private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = new TypeFilter[] {
-        new AnnotationTypeFilter(Entity.class, false),
-        new AnnotationTypeFilter(Embeddable.class, false),
-        new AnnotationTypeFilter(MappedSuperclass.class, false)};
+    private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = [
+        new AnnotationTypeFilter(Entity, false),
+        new AnnotationTypeFilter(Embeddable, false),
+        new AnnotationTypeFilter(MappedSuperclass, false)] as TypeFilter[]
 
-    private static final TypeFilter CONVERTER_TYPE_FILTER = new AnnotationTypeFilter(Converter.class, false);
+    private static final TypeFilter CONVERTER_TYPE_FILTER = new AnnotationTypeFilter(Converter, false)
 
-    private static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore";
+    private static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = 'spring.classformat.ignore'
 
-    private static final boolean shouldIgnoreClassFormatException =
-            SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME);
+    private static final boolean SHOULD_IGNORE_CLASS_FORMAT_EXCEPTION =
+            SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME)
 
-    private final ResourcePatternResolver resourcePatternResolver;
+    private final ResourcePatternResolver resourcePatternResolver
 
-    private TypeFilter[] entityTypeFilters = DEFAULT_ENTITY_TYPE_FILTERS;
+    private TypeFilter[] entityTypeFilters = DEFAULT_ENTITY_TYPE_FILTERS
 
     /**
      * Create a new LocalSessionFactoryBuilder for the given DataSource.
      * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
      * (may be {@code null})
      */
-    public LocalSessionFactoryBuilder(@Nullable DataSource dataSource) {
-        this(dataSource, new PathMatchingResourcePatternResolver());
+    LocalSessionFactoryBuilder(DataSource dataSource) {
+        this(dataSource, new PathMatchingResourcePatternResolver())
     }
 
     /**
@@ -137,8 +130,8 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * (may be {@code null})
      * @param classLoader the ClassLoader to load application classes from
      */
-    public LocalSessionFactoryBuilder(@Nullable DataSource dataSource, ClassLoader classLoader) {
-        this(dataSource, new PathMatchingResourcePatternResolver(classLoader));
+    LocalSessionFactoryBuilder(DataSource dataSource, ClassLoader classLoader) {
+        this(dataSource, new PathMatchingResourcePatternResolver(classLoader))
     }
 
     /**
@@ -147,9 +140,9 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * (may be {@code null})
      * @param resourceLoader the ResourceLoader to load application classes from
      */
-    public LocalSessionFactoryBuilder(@Nullable DataSource dataSource, ResourceLoader resourceLoader) {
+    LocalSessionFactoryBuilder(DataSource dataSource, ResourceLoader resourceLoader) {
         this(dataSource, resourceLoader, new MetadataSources(
-                new BootstrapServiceRegistryBuilder().applyClassLoader(resourceLoader.getClassLoader()).build()));
+                new BootstrapServiceRegistryBuilder().applyClassLoader(resourceLoader.getClassLoader()).build()))
     }
 
     /**
@@ -160,20 +153,20 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @param metadataSources the Hibernate MetadataSources service to use (for example, reusing an existing one)
      * @since 4.3
      */
-    public LocalSessionFactoryBuilder(
-            @Nullable DataSource dataSource, ResourceLoader resourceLoader, MetadataSources metadataSources) {
+    LocalSessionFactoryBuilder(
+            DataSource dataSource, ResourceLoader resourceLoader, MetadataSources metadataSources) {
 
-        super(metadataSources);
+        super(metadataSources)
 
-        getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName());
+        getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.name)
         if (dataSource != null) {
-            getProperties().put(AvailableSettings.DATASOURCE, dataSource);
+            getProperties().put(AvailableSettings.DATASOURCE, dataSource)
         }
         getProperties().put(AvailableSettings.CONNECTION_HANDLING,
-                PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD);
+                PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD)
 
-        getProperties().put(AvailableSettings.CLASSLOADERS, Collections.singleton(resourceLoader.getClassLoader()));
-        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
+        getProperties().put(AvailableSettings.CLASSLOADERS, Collections.singleton(resourceLoader.getClassLoader()))
+        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
     }
 
     /**
@@ -187,39 +180,37 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * <p>Note: If this is set, the Hibernate settings should not contain a JTA platform
      * setting to avoid meaningless double configuration.
      */
-    public LocalSessionFactoryBuilder setJtaTransactionManager(Object jtaTransactionManager) {
-        Assert.notNull(jtaTransactionManager, "Transaction manager reference must not be null");
+    LocalSessionFactoryBuilder setJtaTransactionManager(Object jtaTransactionManager) {
+        Assert.notNull(jtaTransactionManager, 'Transaction manager reference must not be null')
 
-        if (jtaTransactionManager instanceof JtaTransactionManager springJtaTm) {
-            boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader());
+        if (jtaTransactionManager instanceof JtaTransactionManager) {
+            JtaTransactionManager springJtaTm = (JtaTransactionManager) jtaTransactionManager
+            boolean webspherePresent = ClassUtils.isPresent('com.ibm.wsspi.uow.UOWManager', getClass().getClassLoader())
             if (webspherePresent) {
                 getProperties().put(AvailableSettings.JTA_PLATFORM,
-                        "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform");
-            }
-            else {
+                        'org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform')
+            } else {
                 if (springJtaTm.getTransactionManager() == null) {
                     throw new IllegalArgumentException(
-                            "Can only apply JtaTransactionManager which has a TransactionManager reference set");
+                            'Can only apply JtaTransactionManager which has a TransactionManager reference set')
                 }
                 getProperties().put(AvailableSettings.JTA_PLATFORM,
                         new ConfigurableJtaPlatform(springJtaTm.getTransactionManager(), springJtaTm.getUserTransaction(),
-                                springJtaTm.getTransactionSynchronizationRegistry()));
+                                springJtaTm.getTransactionSynchronizationRegistry()))
             }
-        }
-        else if (jtaTransactionManager instanceof TransactionManager jtaTm) {
+        } else if (jtaTransactionManager instanceof TransactionManager) {
             getProperties().put(AvailableSettings.JTA_PLATFORM,
-                    new ConfigurableJtaPlatform(jtaTm, null, null));
-        }
-        else {
+                    new ConfigurableJtaPlatform((TransactionManager) jtaTransactionManager, null, null))
+        } else {
             throw new IllegalArgumentException(
-                    "Unknown transaction manager type: " + jtaTransactionManager.getClass().getName());
+                    'Unknown transaction manager type: ' + jtaTransactionManager.getClass().getName())
         }
 
-        getProperties().put(AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta");
+        getProperties().put(AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, 'jta')
         getProperties().put(AvailableSettings.CONNECTION_HANDLING,
-                PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT);
+                PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT)
 
-        return this;
+        return this
     }
 
     /**
@@ -230,9 +221,9 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @see SpringBeanContainer
      * @see AvailableSettings#BEAN_CONTAINER
      */
-    public LocalSessionFactoryBuilder setBeanContainer(ConfigurableListableBeanFactory beanFactory) {
-        getProperties().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
-        return this;
+    LocalSessionFactoryBuilder setBeanContainer(ConfigurableListableBeanFactory beanFactory) {
+        getProperties().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory))
+        return this
     }
 
     /**
@@ -243,9 +234,9 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @since 5.1
      * @see AvailableSettings#CACHE_REGION_FACTORY
      */
-    public LocalSessionFactoryBuilder setCacheRegionFactory(RegionFactory cacheRegionFactory) {
-        getProperties().put(AvailableSettings.CACHE_REGION_FACTORY, cacheRegionFactory);
-        return this;
+    LocalSessionFactoryBuilder setCacheRegionFactory(RegionFactory cacheRegionFactory) {
+        getProperties().put(AvailableSettings.CACHE_REGION_FACTORY, cacheRegionFactory)
+        return this
     }
 
     /**
@@ -253,9 +244,9 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @since 4.3
      * @see AvailableSettings#MULTI_TENANT_CONNECTION_PROVIDER
      */
-    public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
-        getProperties().put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
-        return this;
+    LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
+        getProperties().put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider)
+        return this
     }
 
     /**
@@ -264,10 +255,10 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @see AvailableSettings#MULTI_TENANT_IDENTIFIER_RESOLVER
      */
     @Override
-    public Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> currentTenantIdentifierResolver) {
-        getProperties().put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
-        super.setCurrentTenantIdentifierResolver(currentTenantIdentifierResolver);
-        return this;
+    Configuration setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver<?> currentTenantIdentifierResolver) {
+        getProperties().put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver)
+        super.setCurrentTenantIdentifierResolver(currentTenantIdentifierResolver)
+        return this
     }
 
     /**
@@ -277,9 +268,9 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * or {@code @jakarta.persistence.MappedSuperclass}.
      * @see #scanPackages
      */
-    public LocalSessionFactoryBuilder setEntityTypeFilters(TypeFilter... entityTypeFilters) {
-        this.entityTypeFilters = entityTypeFilters;
-        return this;
+    LocalSessionFactoryBuilder setEntityTypeFilters(TypeFilter... entityTypeFilters) {
+        this.entityTypeFilters = entityTypeFilters
+        return this
     }
 
     /**
@@ -287,11 +278,11 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @see #addAnnotatedClass
      * @see #scanPackages
      */
-    public LocalSessionFactoryBuilder addAnnotatedClasses(Class<?>... annotatedClasses) {
+    LocalSessionFactoryBuilder addAnnotatedClasses(Class<?>... annotatedClasses) {
         for (Class<?> annotatedClass : annotatedClasses) {
-            addAnnotatedClass(annotatedClass);
+            addAnnotatedClass(annotatedClass)
         }
-        return this;
+        return this
     }
 
     /**
@@ -299,11 +290,11 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @see #addPackage
      * @see #scanPackages
      */
-    public LocalSessionFactoryBuilder addPackages(String... annotatedPackages) {
+    LocalSessionFactoryBuilder addPackages(String... annotatedPackages) {
         for (String annotatedPackage : annotatedPackages) {
-            addPackage(annotatedPackage);
+            addPackage(annotatedPackage)
         }
-        return this;
+        return this
     }
 
     /**
@@ -312,64 +303,57 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @param packagesToScan one or more Java package names
      * @throws HibernateException if scanning fails for any reason
      */
-    @SuppressWarnings("unchecked")
-    public LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws HibernateException {
-        Set<String> entityClassNames = new TreeSet<>();
-        Set<String> converterClassNames = new TreeSet<>();
-        Set<String> packageNames = new TreeSet<>();
+    @SuppressWarnings('unchecked')
+    LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws HibernateException {
+        Set<String> entityClassNames = new TreeSet<>()
+        Set<String> converterClassNames = new TreeSet<>()
+        Set<String> packageNames = new TreeSet<>()
         try {
             for (String pkg : packagesToScan) {
                 String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
-                        ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
-                Resource[] resources = this.resourcePatternResolver.getResources(pattern);
-                MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
+                        ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN
+                Resource[] resources = this.resourcePatternResolver.getResources(pattern)
+                MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver)
                 for (Resource resource : resources) {
                     try {
-                        MetadataReader reader = readerFactory.getMetadataReader(resource);
-                        String className = reader.getClassMetadata().getClassName();
+                        MetadataReader reader = readerFactory.getMetadataReader(resource)
+                        String className = reader.getClassMetadata().getClassName()
                         if (matchesEntityTypeFilter(reader, readerFactory)) {
-                            entityClassNames.add(className);
-                        }
-                        else if (CONVERTER_TYPE_FILTER.match(reader, readerFactory)) {
-                            converterClassNames.add(className);
+                            entityClassNames.add(className)
+                        } else if (CONVERTER_TYPE_FILTER.match(reader, readerFactory)) {
+                            converterClassNames.add(className)
+                        } else if (className.endsWith(PACKAGE_INFO_SUFFIX)) {
+                            packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length()))
                         }
-                        else if (className.endsWith(PACKAGE_INFO_SUFFIX)) {
-                            packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length()));
-                        }
-                    }
-                    catch (FileNotFoundException ex) {
+                    } catch (FileNotFoundException ignored) {
                         // Ignore non-readable resource
-                    }
-                    catch (ClassFormatException ex) {
-                        if (!shouldIgnoreClassFormatException) {
-                            throw new MappingException("Incompatible class format in " + resource, ex);
+                    } catch (ClassFormatException ex) {
+                        if (!SHOULD_IGNORE_CLASS_FORMAT_EXCEPTION) {
+                            throw new MappingException('Incompatible class format in ' + resource, ex)
                         }
-                    }
-                    catch (Throwable ex) {
-                        throw new MappingException("Failed to read candidate component class: " + resource, ex);
+                    } catch (Throwable ex) {
+                        throw new MappingException('Failed to read candidate component class: ' + resource, ex)
                     }
                 }
             }
-        }
-        catch (IOException ex) {
-            throw new MappingException("Failed to scan classpath for unlisted classes", ex);
+        } catch (IOException ex) {
+            throw new MappingException('Failed to scan classpath for unlisted classes', ex)
         }
         try {
-            ClassLoader cl = this.resourcePatternResolver.getClassLoader();
+            ClassLoader cl = this.resourcePatternResolver.getClassLoader()
             for (String className : entityClassNames) {
-                addAnnotatedClass(ClassUtils.forName(className, cl));
+                addAnnotatedClass(ClassUtils.forName(className, cl))
             }
             for (String className : converterClassNames) {
-                addAttributeConverter((Class<? extends AttributeConverter<?, ?>>) ClassUtils.forName(className, cl));
+                addAttributeConverter((Class<? extends AttributeConverter<?, ?>>) ClassUtils.forName(className, cl))
             }
             for (String packageName : packageNames) {
-                addPackage(packageName);
+                addPackage(packageName)
             }
+        } catch (ClassNotFoundException ex) {
+            throw new MappingException('Failed to load annotated classes from classpath', ex)
         }
-        catch (ClassNotFoundException ex) {
-            throw new MappingException("Failed to load annotated classes from classpath", ex);
-        }
-        return this;
+        return this
     }
 
     /**
@@ -379,10 +363,10 @@ public class LocalSessionFactoryBuilder extends Configuration {
     private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
         for (TypeFilter filter : this.entityTypeFilters) {
             if (filter.match(reader, readerFactory)) {
-                return true;
+                return true
             }
         }
-        return false;
+        return false
     }
 
     /**
@@ -399,11 +383,11 @@ public class LocalSessionFactoryBuilder extends Configuration {
      * @since 4.3
      * @see #buildSessionFactory()
      */
-    public SessionFactory buildSessionFactory(AsyncTaskExecutor bootstrapExecutor) {
-        Assert.notNull(bootstrapExecutor, "AsyncTaskExecutor must not be null");
+    SessionFactory buildSessionFactory(AsyncTaskExecutor bootstrapExecutor) {
+        Assert.notNull(bootstrapExecutor, 'AsyncTaskExecutor must not be null')
         return (SessionFactory) Proxy.newProxyInstance(this.resourcePatternResolver.getClassLoader(),
-                new Class<?>[] {SessionFactoryImplementor.class, InfrastructureProxy.class},
-                new BootstrapSessionFactoryInvocationHandler(bootstrapExecutor));
+                [SessionFactoryImplementor, InfrastructureProxy] as Class<?>[],
+                new BootstrapSessionFactoryInvocationHandler(bootstrapExecutor))
     }
 
     /**
@@ -413,54 +397,55 @@ public class LocalSessionFactoryBuilder extends Configuration {
      */
     private class BootstrapSessionFactoryInvocationHandler implements InvocationHandler {
 
-        private final Future<SessionFactory> sessionFactoryFuture;
+        private final Future<SessionFactory> sessionFactoryFuture
 
-        public BootstrapSessionFactoryInvocationHandler(AsyncTaskExecutor bootstrapExecutor) {
+        BootstrapSessionFactoryInvocationHandler(AsyncTaskExecutor bootstrapExecutor) {
             this.sessionFactoryFuture = bootstrapExecutor.submit(
-                    (Callable<SessionFactory>) LocalSessionFactoryBuilder.this::buildSessionFactory);
+                    { -> LocalSessionFactoryBuilder.this.buildSessionFactory() } as Callable<SessionFactory>)
         }
 
         @Override
-        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-            return switch (method.getName()) {
-                // Only consider equal when proxies are identical.
-                case "equals" -> (proxy == args[0]);
-                // Use hashCode of EntityManagerFactory proxy.
-                case "hashCode" -> System.identityHashCode(proxy);
-                case "getProperties" -> getProperties();
-                // Call coming in through InfrastructureProxy interface...
-                case "getWrappedObject" -> getSessionFactory();
-                default -> {
+        Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            switch (method.getName()) {
+                case 'equals':
+                    // Only consider equal when proxies are identical.
+                    return proxy.is(args[0])
+                case 'hashCode':
+                    // Use hashCode of EntityManagerFactory proxy.
+                    return System.identityHashCode(proxy)
+                case 'getProperties':
+                    return LocalSessionFactoryBuilder.this.getProperties()
+                case 'getWrappedObject':
+                    // Call coming in through InfrastructureProxy interface...
+                    return getSessionFactory()
+                default:
                     try {
                         // Regular delegation to the target SessionFactory,
                         // enforcing its full initialization...
-                        yield method.invoke(getSessionFactory(), args);
+                        return method.invoke(getSessionFactory(), args)
+                    } catch (InvocationTargetException ex) {
+                        throw ex.getTargetException()
                     }
-                    catch (InvocationTargetException ex) {
-                        throw ex.getTargetException();
-                    }
-                }
-            };
+            }
         }
 
         private SessionFactory getSessionFactory() {
             try {
-                return this.sessionFactoryFuture.get();
-            }
-            catch (InterruptedException ex) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted during initialization of Hibernate SessionFactory", ex);
-            }
-            catch (ExecutionException ex) {
-                Throwable cause = ex.getCause();
-                if (cause instanceof HibernateException hibernateException) {
+                return this.sessionFactoryFuture.get()
+            } catch (InterruptedException ex) {
+                Thread.currentThread().interrupt()
+                throw new IllegalStateException('Interrupted during initialization of Hibernate SessionFactory', ex)
+            } catch (ExecutionException ex) {
+                Throwable cause = ex.getCause()
+                if (cause instanceof HibernateException) {
                     // Rethrow a provider configuration exception (possibly with a nested cause) directly
-                    throw hibernateException;
+                    throw (HibernateException) cause
                 }
-                throw new IllegalStateException("Failed to asynchronously initialize Hibernate SessionFactory: " +
-                        ex.getMessage(), cause);
+                throw new IllegalStateException('Failed to asynchronously initialize Hibernate SessionFactory: ' +
+                        ex.getMessage(), cause)
             }
         }
+
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionFactoryUtils.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionFactoryUtils.groovy
similarity index 52%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionFactoryUtils.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionFactoryUtils.groovy
index 7d45a02d54..223fb276c2 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionFactoryUtils.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionFactoryUtils.groovy
@@ -13,60 +13,57 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
+import java.lang.reflect.Method
 
-import java.lang.reflect.Method;
-import java.util.Map;
+import javax.sql.DataSource
 
-import javax.sql.DataSource;
-
-import jakarta.persistence.PersistenceException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.HibernateException;
-import org.hibernate.JDBCException;
-import org.hibernate.NonUniqueObjectException;
-import org.hibernate.NonUniqueResultException;
-import org.hibernate.ObjectDeletedException;
-import org.hibernate.PersistentObjectException;
-import org.hibernate.PessimisticLockException;
-import org.hibernate.PropertyValueException;
-import org.hibernate.QueryException;
-import org.hibernate.QueryTimeoutException;
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.hibernate.StaleObjectStateException;
-import org.hibernate.StaleStateException;
-import org.hibernate.TransientObjectException;
-import org.hibernate.UnresolvableObjectException;
-import org.hibernate.WrongClassException;
-import org.hibernate.cfg.Environment;
-import org.hibernate.dialect.lock.OptimisticEntityLockException;
-import org.hibernate.dialect.lock.PessimisticEntityLockException;
-import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
-import org.hibernate.engine.spi.SessionFactoryImplementor;
-import org.hibernate.exception.ConstraintViolationException;
-import org.hibernate.exception.DataException;
-import org.hibernate.exception.JDBCConnectionException;
-import org.hibernate.exception.LockAcquisitionException;
-import org.hibernate.exception.SQLGrammarException;
-import org.hibernate.service.UnknownServiceException;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.dao.CannotAcquireLockException;
-import org.springframework.dao.DataAccessException;
-import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.dao.DataIntegrityViolationException;
-import org.springframework.dao.DuplicateKeyException;
-import org.springframework.dao.IncorrectResultSizeDataAccessException;
-import org.springframework.dao.InvalidDataAccessApiUsageException;
-import org.springframework.dao.InvalidDataAccessResourceUsageException;
-import org.springframework.dao.PessimisticLockingFailureException;
-import org.springframework.jdbc.datasource.DataSourceUtils;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.ReflectionUtils;
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import jakarta.persistence.PersistenceException
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.hibernate.HibernateException
+import org.hibernate.JDBCException
+import org.hibernate.NonUniqueObjectException
+import org.hibernate.NonUniqueResultException
+import org.hibernate.ObjectDeletedException
+import org.hibernate.PersistentObjectException
+import org.hibernate.PessimisticLockException
+import org.hibernate.PropertyValueException
+import org.hibernate.QueryException
+import org.hibernate.QueryTimeoutException
+import org.hibernate.Session
+import org.hibernate.SessionFactory
+import org.hibernate.StaleObjectStateException
+import org.hibernate.StaleStateException
+import org.hibernate.TransientObjectException
+import org.hibernate.UnresolvableObjectException
+import org.hibernate.WrongClassException
+import org.hibernate.cfg.Environment
+import org.hibernate.dialect.lock.OptimisticEntityLockException
+import org.hibernate.dialect.lock.PessimisticEntityLockException
+import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider
+import org.hibernate.engine.spi.SessionFactoryImplementor
+import org.hibernate.exception.ConstraintViolationException
+import org.hibernate.exception.DataException
+import org.hibernate.exception.JDBCConnectionException
+import org.hibernate.exception.LockAcquisitionException
+import org.hibernate.exception.SQLGrammarException
+import org.hibernate.service.UnknownServiceException
+import org.springframework.dao.CannotAcquireLockException
+import org.springframework.dao.DataAccessException
+import org.springframework.dao.DataAccessResourceFailureException
+import org.springframework.dao.DataIntegrityViolationException
+import org.springframework.dao.DuplicateKeyException
+import org.springframework.dao.IncorrectResultSizeDataAccessException
+import org.springframework.dao.InvalidDataAccessApiUsageException
+import org.springframework.dao.InvalidDataAccessResourceUsageException
+import org.springframework.dao.PessimisticLockingFailureException
+import org.springframework.jdbc.datasource.DataSourceUtils
+import org.springframework.util.ClassUtils
+import org.springframework.util.ReflectionUtils
 
 /**
  * Helper class featuring methods for Hibernate Session handling.
@@ -80,7 +77,8 @@ import org.springframework.util.ReflectionUtils;
  * @see HibernateExceptionTranslator
  * @see HibernateTransactionManager
  */
-public abstract class SessionFactoryUtils {
+@CompileStatic
+abstract class SessionFactoryUtils {
 
     /**
      * Order value for TransactionSynchronization objects that clean up Hibernate Sessions.
@@ -88,10 +86,11 @@ public abstract class SessionFactoryUtils {
      * to execute Session cleanup before JDBC Connection cleanup, if any.
      * @see DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
      */
-    public static final int SESSION_SYNCHRONIZATION_ORDER =
-            DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
+    static final int SESSION_SYNCHRONIZATION_ORDER =
+            DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
 
-    static final Log logger = LogFactory.getLog(SessionFactoryUtils.class);
+    @PackageScope
+    static final Log LOGGER = LogFactory.getLog(SessionFactoryUtils)
 
     /**
      * Trigger a flush on the given Hibernate Session, converting regular
@@ -102,26 +101,23 @@ public abstract class SessionFactoryUtils {
      * @throws DataAccessException in case of flush failures
      * @since 4.3.2
      */
+    @PackageScope
     static void flush(Session session, boolean synch) throws DataAccessException {
         if (synch) {
-            logger.debug("Flushing Hibernate Session on transaction synchronization");
-        }
-        else {
-            logger.debug("Flushing Hibernate Session on explicit request");
+            LOGGER.debug('Flushing Hibernate Session on transaction synchronization')
+        } else {
+            LOGGER.debug('Flushing Hibernate Session on explicit request')
         }
         try {
-            session.flush();
-        }
-        catch (HibernateException ex) {
-            throw convertHibernateAccessException(ex);
-        }
-        catch (PersistenceException ex) {
-            if (ex.getCause() instanceof HibernateException hibernateException) {
-                throw convertHibernateAccessException(hibernateException);
+            session.flush()
+        } catch (HibernateException ex) {
+            throw convertHibernateAccessException(ex)
+        } catch (PersistenceException ex) {
+            if (ex.getCause() instanceof HibernateException) {
+                throw convertHibernateAccessException((HibernateException) ex.getCause())
             }
-            throw ex;
+            throw ex
         }
-
     }
 
     /**
@@ -130,15 +126,14 @@ public abstract class SessionFactoryUtils {
      * @param session the Hibernate Session to close (may be {@code null})
      * @see Session#close()
      */
-    public static void closeSession(@Nullable Session session) {
+    static void closeSession(Session session) {
         if (session != null) {
             try {
                 if (session.isOpen()) {
-                    session.close();
+                    session.close()
                 }
-            }
-            catch (Throwable ex) {
-                logger.error("Failed to release Hibernate Session", ex);
+            } catch (Throwable ex) {
+                LOGGER.error('Failed to release Hibernate Session', ex)
             }
         }
     }
@@ -149,32 +144,31 @@ public abstract class SessionFactoryUtils {
      * @return the DataSource, or {@code null} if none found
      * @see ConnectionProvider
      */
-    @Nullable
-    public static DataSource getDataSource(SessionFactory sessionFactory) {
-        Method getProperties = ClassUtils.getMethodIfAvailable(sessionFactory.getClass(), "getProperties");
+    static DataSource getDataSource(SessionFactory sessionFactory) {
+        Method getProperties = ClassUtils.getMethodIfAvailable(sessionFactory.getClass(), 'getProperties')
         if (getProperties != null) {
-            Map<?, ?> props = (Map<?, ?>) ReflectionUtils.invokeMethod(getProperties, sessionFactory);
+            Map<?, ?> props = (Map<?, ?>) ReflectionUtils.invokeMethod(getProperties, sessionFactory)
             if (props != null) {
-                Object dataSourceValue = props.get(Environment.DATASOURCE);
-                if (dataSourceValue instanceof DataSource dataSource) {
-                    return dataSource;
+                Object dataSourceValue = props.get(Environment.DATASOURCE)
+                if (dataSourceValue instanceof DataSource) {
+                    return (DataSource) dataSourceValue
                 }
             }
         }
-        if (sessionFactory instanceof SessionFactoryImplementor sfi) {
+        if (sessionFactory instanceof SessionFactoryImplementor) {
+            SessionFactoryImplementor sfi = (SessionFactoryImplementor) sessionFactory
             try {
-                ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class);
+                ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider)
                 if (cp != null) {
-                    return cp.unwrap(DataSource.class);
+                    return cp.unwrap(DataSource)
                 }
-            }
-            catch (UnknownServiceException ex) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("No ConnectionProvider found - cannot determine DataSource for SessionFactory: " + ex);
+            } catch (UnknownServiceException ex) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug('No ConnectionProvider found - cannot determine DataSource for SessionFactory: ' + ex)
                 }
             }
         }
-        return null;
+        return null
     }
 
     /**
@@ -185,79 +179,85 @@ public abstract class SessionFactoryUtils {
      * @see HibernateExceptionTranslator#convertHibernateAccessException
      * @see HibernateTransactionManager#convertHibernateAccessException
      */
-    public static DataAccessException convertHibernateAccessException(HibernateException ex) {
+    static DataAccessException convertHibernateAccessException(HibernateException ex) {
         if (ex instanceof JDBCConnectionException) {
-            return new DataAccessResourceFailureException(ex.getMessage(), ex);
+            return new DataAccessResourceFailureException(ex.getMessage(), ex)
         }
-        if (ex instanceof SQLGrammarException hibJdbcEx) {
-            return new InvalidDataAccessResourceUsageException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+        if (ex instanceof SQLGrammarException) {
+            SQLGrammarException hibJdbcEx = (SQLGrammarException) ex
+            return new InvalidDataAccessResourceUsageException(ex.getMessage() + '; SQL [' + hibJdbcEx.getSQL() + ']', ex)
         }
-        if (ex instanceof QueryTimeoutException hibJdbcEx) {
-            return new org.springframework.dao.QueryTimeoutException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+        if (ex instanceof QueryTimeoutException) {
+            QueryTimeoutException hibJdbcEx = (QueryTimeoutException) ex
+            return new org.springframework.dao.QueryTimeoutException(ex.getMessage() + '; SQL [' + hibJdbcEx.getSQL() + ']', ex)
         }
-        if (ex instanceof LockAcquisitionException hibJdbcEx) {
-            return new CannotAcquireLockException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+        if (ex instanceof LockAcquisitionException) {
+            LockAcquisitionException hibJdbcEx = (LockAcquisitionException) ex
+            return new CannotAcquireLockException(ex.getMessage() + '; SQL [' + hibJdbcEx.getSQL() + ']', ex)
         }
-        if (ex instanceof PessimisticLockException hibJdbcEx) {
-            return new PessimisticLockingFailureException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+        if (ex instanceof PessimisticLockException) {
+            PessimisticLockException hibJdbcEx = (PessimisticLockException) ex
+            return new PessimisticLockingFailureException(ex.getMessage() + '; SQL [' + hibJdbcEx.getSQL() + ']', ex)
         }
-        if (ex instanceof ConstraintViolationException hibJdbcEx) {
-            return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() +
-                    "]; constraint [" + hibJdbcEx.getConstraintName() + "]", ex);
+        if (ex instanceof ConstraintViolationException) {
+            ConstraintViolationException hibJdbcEx = (ConstraintViolationException) ex
+            return new DataIntegrityViolationException(ex.getMessage() + '; SQL [' + hibJdbcEx.getSQL() +
+                    ']; constraint [' + hibJdbcEx.getConstraintName() + ']', ex)
         }
-        if (ex instanceof DataException hibJdbcEx) {
-            return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+        if (ex instanceof DataException) {
+            DataException hibJdbcEx = (DataException) ex
+            return new DataIntegrityViolationException(ex.getMessage() + '; SQL [' + hibJdbcEx.getSQL() + ']', ex)
         }
-        if (ex instanceof JDBCException hibJdbcEx) {
-            return new HibernateJdbcException(hibJdbcEx);
+        if (ex instanceof JDBCException) {
+            return new HibernateJdbcException((JDBCException) ex)
         }
         // end of JDBCException (subclass) handling
 
-        if (ex instanceof QueryException queryException) {
-            return new HibernateQueryException(queryException);
+        if (ex instanceof QueryException) {
+            return new HibernateQueryException((QueryException) ex)
         }
         if (ex instanceof NonUniqueResultException) {
-            return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);
+            return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex)
         }
         if (ex instanceof NonUniqueObjectException) {
-            return new DuplicateKeyException(ex.getMessage(), ex);
+            return new DuplicateKeyException(ex.getMessage(), ex)
         }
         if (ex instanceof PropertyValueException) {
-            return new DataIntegrityViolationException(ex.getMessage(), ex);
+            return new DataIntegrityViolationException(ex.getMessage(), ex)
         }
         if (ex instanceof PersistentObjectException) {
-            return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+            return new InvalidDataAccessApiUsageException(ex.getMessage(), ex)
         }
         if (ex instanceof TransientObjectException) {
-            return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+            return new InvalidDataAccessApiUsageException(ex.getMessage(), ex)
         }
         if (ex instanceof ObjectDeletedException) {
-            return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+            return new InvalidDataAccessApiUsageException(ex.getMessage(), ex)
         }
-        if (ex instanceof UnresolvableObjectException unresolvableObjectException) {
-            return new HibernateObjectRetrievalFailureException(unresolvableObjectException);
+        if (ex instanceof UnresolvableObjectException) {
+            return new HibernateObjectRetrievalFailureException((UnresolvableObjectException) ex)
         }
-        if (ex instanceof WrongClassException wrongClassException) {
-            return new HibernateObjectRetrievalFailureException(wrongClassException);
+        if (ex instanceof WrongClassException) {
+            return new HibernateObjectRetrievalFailureException((WrongClassException) ex)
         }
-        if (ex instanceof StaleObjectStateException staleObjectStateException) {
-            return new HibernateOptimisticLockingFailureException(staleObjectStateException);
+        if (ex instanceof StaleObjectStateException) {
+            return new HibernateOptimisticLockingFailureException((StaleObjectStateException) ex)
         }
-        if (ex instanceof StaleStateException staleStateException) {
-            return new HibernateOptimisticLockingFailureException(staleStateException);
+        if (ex instanceof StaleStateException) {
+            return new HibernateOptimisticLockingFailureException((StaleStateException) ex)
         }
-        if (ex instanceof OptimisticEntityLockException optimisticEntityLockException) {
-            return new HibernateOptimisticLockingFailureException(optimisticEntityLockException);
+        if (ex instanceof OptimisticEntityLockException) {
+            return new HibernateOptimisticLockingFailureException((OptimisticEntityLockException) ex)
         }
         if (ex instanceof PessimisticEntityLockException) {
             if (ex.getCause() instanceof LockAcquisitionException) {
-                return new CannotAcquireLockException(ex.getMessage(), ex.getCause());
+                return new CannotAcquireLockException(ex.getMessage(), ex.getCause())
             }
-            return new PessimisticLockingFailureException(ex.getMessage(), ex);
+            return new PessimisticLockingFailureException(ex.getMessage(), ex)
         }
 
         // fallback
-        return new HibernateSystemException(ex);
+        return new HibernateSystemException(ex)
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringBeanContainer.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringBeanContainer.groovy
similarity index 55%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringBeanContainer.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringBeanContainer.groovy
index 2c505e5dd9..3d6fabfd24 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringBeanContainer.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringBeanContainer.groovy
@@ -13,26 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
+import java.util.function.Consumer
 
-import java.util.Map;
-import java.util.function.Consumer;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.resource.beans.container.spi.BeanContainer;
-import org.hibernate.resource.beans.container.spi.ContainedBean;
-import org.hibernate.resource.beans.spi.BeanInstanceProducer;
-import org.hibernate.type.spi.TypeBootstrapContext;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.BeanCreationException;
-import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.util.Assert;
-import org.springframework.util.ConcurrentReferenceHashMap;
+import groovy.transform.CompileStatic
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.hibernate.resource.beans.container.spi.BeanContainer
+import org.hibernate.resource.beans.container.spi.BeanContainer.LifecycleOptions
+import org.hibernate.resource.beans.container.spi.ContainedBean
+import org.hibernate.resource.beans.spi.BeanInstanceProducer
+import org.hibernate.type.spi.TypeBootstrapContext
+import org.springframework.beans.BeansException
+import org.springframework.beans.factory.BeanCreationException
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
+import org.springframework.util.Assert
+import org.springframework.util.ConcurrentReferenceHashMap
 
 /**
  * Spring's implementation of Hibernate's {@link BeanContainer} SPI,
@@ -75,65 +73,64 @@ import org.springframework.util.ConcurrentReferenceHashMap;
  * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setJpaPropertyMap
  * @see org.hibernate.cfg.AvailableSettings#BEAN_CONTAINER
  */
-public final class SpringBeanContainer implements BeanContainer {
+@CompileStatic
+final class SpringBeanContainer implements BeanContainer {
 
-    private static final Log logger = LogFactory.getLog(SpringBeanContainer.class);
+    private static final Log LOGGER = LogFactory.getLog(SpringBeanContainer)
 
-    private final ConfigurableListableBeanFactory beanFactory;
+    private final ConfigurableListableBeanFactory beanFactory
 
-    private final Map<Object, SpringContainedBean<?>> beanCache = new ConcurrentReferenceHashMap<>();
+    private final Map<Object, SpringContainedBean<?>> beanCache = new ConcurrentReferenceHashMap<>()
 
     /**
      * Instantiate a new SpringBeanContainer for the given bean factory.
      * @param beanFactory the Spring bean factory to delegate to
      */
-    public SpringBeanContainer(ConfigurableListableBeanFactory beanFactory) {
-        Assert.notNull(beanFactory, "ConfigurableListableBeanFactory is required");
-        this.beanFactory = beanFactory;
+    SpringBeanContainer(ConfigurableListableBeanFactory beanFactory) {
+        Assert.notNull(beanFactory, 'ConfigurableListableBeanFactory is required')
+        this.beanFactory = beanFactory
     }
 
     @Override
-    @SuppressWarnings("unchecked")
-    public <B> ContainedBean<B> getBean(
+    @SuppressWarnings('unchecked')
+    def <B> ContainedBean<B> getBean(
             Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
 
-        SpringContainedBean<?> bean;
+        SpringContainedBean<?> bean
         if (lifecycleOptions.canUseCachedReferences()) {
-            bean = this.beanCache.get(beanType);
+            bean = this.beanCache.get(beanType)
             if (bean == null) {
-                bean = createBean(beanType, lifecycleOptions, fallbackProducer);
-                this.beanCache.put(beanType, bean);
+                bean = createBean(beanType, lifecycleOptions, fallbackProducer)
+                this.beanCache.put(beanType, bean)
             }
+        } else {
+            bean = createBean(beanType, lifecycleOptions, fallbackProducer)
         }
-        else {
-            bean = createBean(beanType, lifecycleOptions, fallbackProducer);
-        }
-        return (SpringContainedBean<B>) bean;
+        return (SpringContainedBean<B>) bean
     }
 
     @Override
-    @SuppressWarnings("unchecked")
-    public <B> ContainedBean<B> getBean(
+    @SuppressWarnings('unchecked')
+    def <B> ContainedBean<B> getBean(
             String name, Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
 
-        SpringContainedBean<?> bean;
+        SpringContainedBean<?> bean
         if (lifecycleOptions.canUseCachedReferences()) {
-            bean = this.beanCache.get(name);
+            bean = this.beanCache.get(name)
             if (bean == null) {
-                bean = createBean(name, beanType, lifecycleOptions, fallbackProducer);
-                this.beanCache.put(name, bean);
+                bean = createBean(name, beanType, lifecycleOptions, fallbackProducer)
+                this.beanCache.put(name, bean)
             }
+        } else {
+            bean = createBean(name, beanType, lifecycleOptions, fallbackProducer)
         }
-        else {
-            bean = createBean(name, beanType, lifecycleOptions, fallbackProducer);
-        }
-        return (SpringContainedBean<B>) bean;
+        return (SpringContainedBean<B>) bean
     }
 
     @Override
-    public void stop() {
-        this.beanCache.values().forEach(SpringContainedBean::destroyIfNecessary);
-        this.beanCache.clear();
+    void stop() {
+        this.beanCache.values().forEach { SpringContainedBean<?> bean -> bean.destroyIfNecessary() }
+        this.beanCache.clear()
     }
 
     private SpringContainedBean<?> createBean(
@@ -141,33 +138,29 @@ public final class SpringBeanContainer implements BeanContainer {
 
         try {
             if (lifecycleOptions.useJpaCompliantCreation()) {
-                return new SpringContainedBean<>(
+                return new SpringContainedBean<Object>(
                         this.beanFactory.createBean(beanType),
-                        this.beanFactory::destroyBean);
+                        { Object beanInstance -> this.beanFactory.destroyBean(beanInstance) })
+            } else {
+                return new SpringContainedBean<Object>(this.beanFactory.getBean(beanType))
             }
-            else {
-                return new SpringContainedBean<>(this.beanFactory.getBean(beanType));
-            }
-        }
-        catch (BeansException ex) {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Falling back to Hibernate's default producer after bean creation failure for " +
-                        beanType + ": " + ex);
+        } catch (BeansException ex) {
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug('Falling back to Hibernate\'s default producer after bean creation failure for ' +
+                        beanType + ': ' + ex)
             }
             try {
-                return new SpringContainedBean<>(fallbackProducer.produceBeanInstance(beanType));
-            }
-            catch (RuntimeException ex2) {
+                return new SpringContainedBean<Object>(fallbackProducer.produceBeanInstance(beanType))
+            } catch (RuntimeException ex2) {
                 if (ex instanceof BeanCreationException) {
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Fallback producer failed for " + beanType + ": " + ex2);
+                    if (LOGGER.isDebugEnabled()) {
+                        LOGGER.debug('Fallback producer failed for ' + beanType + ': ' + ex2)
                     }
                     // Rethrow original Spring exception from first attempt.
-                    throw ex;
-                }
-                else {
+                    throw ex
+                } else {
                     // Throw fallback producer exception since original was probably NoSuchBeanDefinitionException.
-                    throw ex2;
+                    throw ex2
                 }
             }
         }
@@ -178,58 +171,54 @@ public final class SpringBeanContainer implements BeanContainer {
 
         try {
             if (lifecycleOptions.useJpaCompliantCreation()) {
-                Object bean = null;
+                Object bean = null
                 if (fallbackProducer instanceof TypeBootstrapContext) {
                     // Special Hibernate type construction rules, including TypeBootstrapContext resolution.
-                    bean = fallbackProducer.produceBeanInstance(name, beanType);
+                    bean = fallbackProducer.produceBeanInstance(name, beanType)
                 }
                 if (this.beanFactory.containsBean(name)) {
                     if (bean == null) {
-                        bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
+                        bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false)
                     }
-                    this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
-                    this.beanFactory.applyBeanPropertyValues(bean, name);
-                    bean = this.beanFactory.initializeBean(bean, name);
-                    return new SpringContainedBean<>(bean, beanInstance -> this.beanFactory.destroyBean(name, beanInstance));
-                }
-                else if (bean != null) {
+                    this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false)
+                    this.beanFactory.applyBeanPropertyValues(bean, name)
+                    bean = this.beanFactory.initializeBean(bean, name)
+                    return new SpringContainedBean<Object>(bean,
+                            { Object beanInstance -> this.beanFactory.destroyBean(name, beanInstance) })
+                } else if (bean != null) {
                     // No bean found by name but constructed with TypeBootstrapContext rules
-                    this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
-                    bean = this.beanFactory.initializeBean(bean, name);
-                    return new SpringContainedBean<>(bean, this.beanFactory::destroyBean);
-                }
-                else {
+                    this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false)
+                    bean = this.beanFactory.initializeBean(bean, name)
+                    return new SpringContainedBean<Object>(bean,
+                            { Object beanInstance -> this.beanFactory.destroyBean(beanInstance) })
+                } else {
                     // No bean found by name -> construct by type using createBean
-                    return new SpringContainedBean<>(
+                    return new SpringContainedBean<Object>(
                             this.beanFactory.createBean(beanType),
-                            this.beanFactory::destroyBean);
+                            { Object beanInstance -> this.beanFactory.destroyBean(beanInstance) })
                 }
-            }
-            else {
+            } else {
                 return (this.beanFactory.containsBean(name) ?
-                        new SpringContainedBean<>(this.beanFactory.getBean(name, beanType)) :
-                        new SpringContainedBean<>(this.beanFactory.getBean(beanType)));
+                        new SpringContainedBean<Object>(this.beanFactory.getBean(name, beanType)) :
+                        new SpringContainedBean<Object>(this.beanFactory.getBean(beanType)))
             }
-        }
-        catch (BeansException ex) {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Falling back to Hibernate's default producer after bean creation failure for " +
-                        beanType + " with name '" + name + "': " + ex);
+        } catch (BeansException ex) {
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug('Falling back to Hibernate\'s default producer after bean creation failure for ' +
+                        beanType + ' with name \'' + name + '\': ' + ex)
             }
             try {
-                return new SpringContainedBean<>(fallbackProducer.produceBeanInstance(name, beanType));
-            }
-            catch (RuntimeException ex2) {
+                return new SpringContainedBean<Object>(fallbackProducer.produceBeanInstance(name, beanType))
+            } catch (RuntimeException ex2) {
                 if (ex instanceof BeanCreationException) {
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Fallback producer failed for " + beanType + " with name '" + name + "': " + ex2);
+                    if (LOGGER.isDebugEnabled()) {
+                        LOGGER.debug('Fallback producer failed for ' + beanType + ' with name \'' + name + '\': ' + ex2)
                     }
                     // Rethrow original Spring exception from first attempt.
-                    throw ex;
-                }
-                else {
+                    throw ex
+                } else {
                     // Throw fallback producer exception since original was probably NoSuchBeanDefinitionException.
-                    throw ex2;
+                    throw ex2
                 }
             }
         }
@@ -237,36 +226,36 @@ public final class SpringBeanContainer implements BeanContainer {
 
     private static final class SpringContainedBean<B> implements ContainedBean<B> {
 
-        private final B beanInstance;
+        private final B beanInstance
 
-        @Nullable
-        private Consumer<B> destructionCallback;
+        private Consumer<B> destructionCallback
 
-        public SpringContainedBean(B beanInstance) {
-            this.beanInstance = beanInstance;
+        SpringContainedBean(B beanInstance) {
+            this.beanInstance = beanInstance
         }
 
-        public SpringContainedBean(B beanInstance, Consumer<B> destructionCallback) {
-            this.beanInstance = beanInstance;
-            this.destructionCallback = destructionCallback;
+        SpringContainedBean(B beanInstance, Consumer<B> destructionCallback) {
+            this.beanInstance = beanInstance
+            this.destructionCallback = destructionCallback
         }
 
         @Override
-        public B getBeanInstance() {
-            return this.beanInstance;
+        B getBeanInstance() {
+            return this.beanInstance
         }
 
         @Override
-        @SuppressWarnings("unchecked")
-        public Class<B> getBeanClass() {
-            return this.beanInstance != null ? (Class<B>) this.beanInstance.getClass() : null;
+        @SuppressWarnings('unchecked')
+        Class<B> getBeanClass() {
+            return this.beanInstance != null ? (Class<B>) this.beanInstance.getClass() : null
         }
 
-        public void destroyIfNecessary() {
+        void destroyIfNecessary() {
             if (this.destructionCallback != null) {
-                this.destructionCallback.accept(this.beanInstance);
+                this.destructionCallback.accept(this.beanInstance)
             }
         }
+
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/OpenSessionInViewInterceptor.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/OpenSessionInViewInterceptor.groovy
similarity index 70%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/OpenSessionInViewInterceptor.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/OpenSessionInViewInterceptor.groovy
index 4dd7c0c479..5c01cda6d0 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/OpenSessionInViewInterceptor.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/OpenSessionInViewInterceptor.groovy
@@ -13,30 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.grails.orm.hibernate.support.hibernate7.support;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.FlushMode;
-import org.hibernate.HibernateException;
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.dao.DataAccessException;
-import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-import org.springframework.ui.ModelMap;
-import org.springframework.util.Assert;
-import org.springframework.web.context.request.AsyncWebRequestInterceptor;
-import org.springframework.web.context.request.WebRequest;
-import org.springframework.web.context.request.async.CallableProcessingInterceptor;
-import org.springframework.web.context.request.async.WebAsyncManager;
-import org.springframework.web.context.request.async.WebAsyncUtils;
-
-import org.grails.orm.hibernate.support.hibernate7.SessionFactoryUtils;
-import org.grails.orm.hibernate.support.hibernate7.SessionHolder;
+package org.grails.orm.hibernate.support.hibernate7.support
+
+import groovy.transform.CompileStatic
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.hibernate.FlushMode
+import org.hibernate.HibernateException
+import org.hibernate.Session
+import org.hibernate.SessionFactory
+import org.springframework.dao.DataAccessException
+import org.springframework.dao.DataAccessResourceFailureException
+import org.springframework.transaction.support.TransactionSynchronizationManager
+import org.springframework.ui.ModelMap
+import org.springframework.util.Assert
+import org.springframework.web.context.request.AsyncWebRequestInterceptor
+import org.springframework.web.context.request.WebRequest
+import org.springframework.web.context.request.async.CallableProcessingInterceptor
+import org.springframework.web.context.request.async.WebAsyncManager
+import org.springframework.web.context.request.async.WebAsyncUtils
+
+import org.grails.orm.hibernate.support.hibernate7.SessionFactoryUtils
+import org.grails.orm.hibernate.support.hibernate7.SessionHolder
 
 /**
  * Spring web request interceptor that binds a Hibernate {@code Session} to the
@@ -69,7 +67,8 @@ import org.grails.orm.hibernate.support.hibernate7.SessionHolder;
  * @see TransactionSynchronizationManager
  * @see SessionFactory#getCurrentSession()
  */
-public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor {
+@CompileStatic
+class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor {
 
     /**
      * Suffix that gets appended to the {@code SessionFactory}
@@ -77,32 +76,30 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
      * session handling" request attribute.
      * @see #getParticipateAttributeName
      */
-    public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
+    static final String PARTICIPATE_SUFFIX = '.PARTICIPATE'
 
-    protected final Log logger = LogFactory.getLog(getClass());
+    protected final Log logger = LogFactory.getLog(getClass())
 
-    @Nullable
-    private SessionFactory sessionFactory;
+    private SessionFactory sessionFactory
 
     /**
      * Set the Hibernate SessionFactory that should be used to create Hibernate Sessions.
      */
-    public void setSessionFactory(@Nullable SessionFactory sessionFactory) {
-        this.sessionFactory = sessionFactory;
+    void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory
     }
 
     /**
      * Return the Hibernate SessionFactory that should be used to create Hibernate Sessions.
      */
-    @Nullable
-    public SessionFactory getSessionFactory() {
-        return this.sessionFactory;
+    SessionFactory getSessionFactory() {
+        return this.sessionFactory
     }
 
     private SessionFactory obtainSessionFactory() {
-        SessionFactory sf = getSessionFactory();
-        Assert.state(sf != null, "No SessionFactory set");
-        return sf;
+        SessionFactory sf = getSessionFactory()
+        Assert.state(sf != null, 'No SessionFactory set')
+        return sf
     }
 
     /**
@@ -110,34 +107,34 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
      * {@link TransactionSynchronizationManager}.
      */
     @Override
-    public void preHandle(WebRequest request) throws DataAccessException {
-        String key = getParticipateAttributeName();
-        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
+    void preHandle(WebRequest request) throws DataAccessException {
+        String key = getParticipateAttributeName()
+        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request)
         if (asyncManager.hasConcurrentResult() && applySessionBindingInterceptor(asyncManager, key)) {
-            return;
+            return
         }
 
         if (TransactionSynchronizationManager.hasResource(obtainSessionFactory())) {
             // Do not modify the Session: just mark the request accordingly.
-            Integer count = (Integer) request.getAttribute(key, WebRequest.SCOPE_REQUEST);
-            int newCount = (count != null ? count + 1 : 1);
-            request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
-        }
-        else {
-            logger.debug("Opening Hibernate Session in OpenSessionInViewInterceptor");
-            Session session = openSession();
-            SessionHolder sessionHolder = new SessionHolder(session);
-            TransactionSynchronizationManager.bindResource(obtainSessionFactory(), sessionHolder);
+            Integer count = (Integer) request.getAttribute(key, WebRequest.SCOPE_REQUEST)
+            int newCount = (count != null ? count + 1 : 1)
+            request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST)
+        } else {
+            logger.debug('Opening Hibernate Session in OpenSessionInViewInterceptor')
+            Session session = openSession()
+            SessionHolder sessionHolder = new SessionHolder(session)
+            TransactionSynchronizationManager.bindResource(obtainSessionFactory(), sessionHolder)
 
             AsyncRequestInterceptor asyncRequestInterceptor =
-                    new AsyncRequestInterceptor(obtainSessionFactory(), sessionHolder);
-            asyncManager.registerCallableInterceptor(key, asyncRequestInterceptor);
-            asyncManager.registerDeferredResultInterceptor(key, asyncRequestInterceptor);
+                    new AsyncRequestInterceptor(obtainSessionFactory(), sessionHolder)
+            asyncManager.registerCallableInterceptor(key, asyncRequestInterceptor)
+            asyncManager.registerDeferredResultInterceptor(key, asyncRequestInterceptor)
         }
     }
 
     @Override
-    public void postHandle(WebRequest request, @Nullable ModelMap model) {
+    void postHandle(WebRequest request, ModelMap model) {
+        // no-op
     }
 
     /**
@@ -145,35 +142,34 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
      * @see TransactionSynchronizationManager
      */
     @Override
-    public void afterCompletion(WebRequest request, @Nullable Exception ex) throws DataAccessException {
+    void afterCompletion(WebRequest request, Exception ex) throws DataAccessException {
         if (!decrementParticipateCount(request)) {
             SessionHolder sessionHolder =
-                    (SessionHolder) TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
-            logger.debug("Closing Hibernate Session in OpenSessionInViewInterceptor");
-            SessionFactoryUtils.closeSession(sessionHolder.getSession());
+                    (SessionHolder) TransactionSynchronizationManager.unbindResource(obtainSessionFactory())
+            logger.debug('Closing Hibernate Session in OpenSessionInViewInterceptor')
+            SessionFactoryUtils.closeSession(sessionHolder.getSession())
         }
     }
 
     private boolean decrementParticipateCount(WebRequest request) {
-        String participateAttributeName = getParticipateAttributeName();
-        Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
+        String participateAttributeName = getParticipateAttributeName()
+        Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST)
         if (count == null) {
-            return false;
+            return false
         }
         // Do not modify the Session: just clear the marker.
         if (count > 1) {
-            request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST);
+            request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST)
+        } else {
+            request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST)
         }
-        else {
-            request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
-        }
-        return true;
+        return true
     }
 
     @Override
-    public void afterConcurrentHandlingStarted(WebRequest request) {
+    void afterConcurrentHandlingStarted(WebRequest request) {
         if (!decrementParticipateCount(request)) {
-            TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
+            TransactionSynchronizationManager.unbindResource(obtainSessionFactory())
         }
     }
 
@@ -187,12 +183,11 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
      */
     protected Session openSession() throws DataAccessResourceFailureException {
         try {
-            Session session = obtainSessionFactory().openSession();
-            session.setHibernateFlushMode(FlushMode.MANUAL);
-            return session;
-        }
-        catch (HibernateException ex) {
-            throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
+            Session session = obtainSessionFactory().openSession()
+            session.setHibernateFlushMode(FlushMode.MANUAL)
+            return session
+        } catch (HibernateException ex) {
+            throw new DataAccessResourceFailureException('Could not open Hibernate Session', ex)
         }
     }
 
@@ -203,16 +198,16 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
      * of the {@code SessionFactory} instance and appends {@link #PARTICIPATE_SUFFIX}.
      */
     protected String getParticipateAttributeName() {
-        return obtainSessionFactory().toString() + PARTICIPATE_SUFFIX;
+        return obtainSessionFactory().toString() + PARTICIPATE_SUFFIX
     }
 
     private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, String key) {
-        CallableProcessingInterceptor cpi = asyncManager.getCallableInterceptor(key);
+        CallableProcessingInterceptor cpi = asyncManager.getCallableInterceptor(key)
         if (cpi == null) {
-            return false;
+            return false
         }
-        ((AsyncRequestInterceptor) cpi).bindSession();
-        return true;
+        ((AsyncRequestInterceptor) cpi).bindSession()
+        return true
     }
 
 }


(grails-core) 02/14: Convert BsonQuery to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 7a430235afc3fc4166aedacdd68d6f561ef42299
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 16:48:26 2026 -0500

    Convert BsonQuery to Groovy
    
    Completes the bson module: BsonQuery, the base of MongoQuery, with its
    static registry of criterion, operator and projection handlers.
    
    The Query criterion classes it uses unqualified are imported explicitly,
    since Groovy does not resolve a superclass's nested types by simple name.
    The "in" parameter is renamed (a Groovy keyword). The private static
    helpers invoked from the anonymous handler classes are @PackageScope so
    those nested classes can reach them. The CustomTypeMarshaller locals are
    declared with concrete type arguments (Q = Document): the interface is
    exposed as CustomTypeMarshaller<?, ?, ?> and the static type checker
    mis-resolves the wildcard capture of query(..., Q) otherwise.
    
    Verified: compileGroovy, codenarc and checkstyle clean; bson suite (33)
    and the full core suite (669, 45 skipped) identical to baseline.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 .../grails/datastore/bson/query/BsonQuery.groovy   | 931 +++++++++++++++++++++
 .../org/grails/datastore/bson/query/BsonQuery.java | 901 --------------------
 2 files changed, 931 insertions(+), 901 deletions(-)

diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.groovy b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.groovy
new file mode 100644
index 0000000000..f6b7f758d8
--- /dev/null
+++ b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.groovy
@@ -0,0 +1,931 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.grails.datastore.bson.query
+
+import java.util.regex.Pattern
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.bson.BsonReader
+import org.bson.BsonType
+import org.bson.Document
+import org.bson.codecs.EncoderContext
+import org.bson.codecs.configuration.CodecRegistry
+import org.codehaus.groovy.runtime.NullObject
+import org.springframework.dao.InvalidDataAccessResourceUsageException
+
+import grails.gorm.DetachedCriteria
+import org.grails.datastore.bson.codecs.CodecCustomTypeMarshaller
+import org.grails.datastore.mapping.config.Property
+import org.grails.datastore.mapping.core.Session
+import org.grails.datastore.mapping.engine.internal.MappingUtils
+import org.grails.datastore.mapping.engine.types.CustomTypeMarshaller
+import org.grails.datastore.mapping.model.MappingContext
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.datastore.mapping.model.PersistentProperty
+import org.grails.datastore.mapping.model.types.Custom
+import org.grails.datastore.mapping.model.types.Embedded
+import org.grails.datastore.mapping.model.types.ToOne
+import org.grails.datastore.mapping.proxy.ProxyHandler
+import org.grails.datastore.mapping.query.Query
+import org.grails.datastore.mapping.query.Query.Between
+import org.grails.datastore.mapping.query.Query.Conjunction
+import org.grails.datastore.mapping.query.Query.Criterion
+import org.grails.datastore.mapping.query.Query.Disjunction
+import org.grails.datastore.mapping.query.Query.Equals
+import org.grails.datastore.mapping.query.Query.EqualsProperty
+import org.grails.datastore.mapping.query.Query.GreaterThan
+import org.grails.datastore.mapping.query.Query.GreaterThanEquals
+import org.grails.datastore.mapping.query.Query.GreaterThanEqualsProperty
+import org.grails.datastore.mapping.query.Query.GreaterThanProperty
+import org.grails.datastore.mapping.query.Query.ILike
+import org.grails.datastore.mapping.query.Query.IdEquals
+import org.grails.datastore.mapping.query.Query.In
+import org.grails.datastore.mapping.query.Query.IsNotNull
+import org.grails.datastore.mapping.query.Query.IsNull
+import org.grails.datastore.mapping.query.Query.Junction
+import org.grails.datastore.mapping.query.Query.LessThan
+import org.grails.datastore.mapping.query.Query.LessThanEquals
+import org.grails.datastore.mapping.query.Query.LessThanEqualsProperty
+import org.grails.datastore.mapping.query.Query.LessThanProperty
+import org.grails.datastore.mapping.query.Query.Like
+import org.grails.datastore.mapping.query.Query.Negation
+import org.grails.datastore.mapping.query.Query.NotEquals
+import org.grails.datastore.mapping.query.Query.NotEqualsProperty
+import org.grails.datastore.mapping.query.Query.Projection
+import org.grails.datastore.mapping.query.Query.PropertyCriterion
+import org.grails.datastore.mapping.query.Query.PropertyNameCriterion
+import org.grails.datastore.mapping.query.Query.RLike
+import org.grails.datastore.mapping.query.Query.SizeEquals
+import org.grails.datastore.mapping.query.Query.SizeGreaterThan
+import org.grails.datastore.mapping.query.Query.SizeGreaterThanEquals
+import org.grails.datastore.mapping.query.Query.SizeLessThan
+import org.grails.datastore.mapping.query.Query.SizeLessThanEquals
+import org.grails.datastore.mapping.query.Query.SizeNotEquals
+import org.grails.datastore.mapping.reflect.EntityReflector
+
+/**
+ * A base class for Query implementations that create BSON queries based on MongoDB query format. See https://docs.mongodb.com/manual/tutorial/query-documents/
+ *
+ * @author Graeme Rocher
+ * @since 6.0
+ */
+@CompileStatic
+abstract class BsonQuery extends Query {
+
+    public static final String PROJECT_OPERATOR = '$project'
+    public static final String SORT_OPERATOR = '$sort'
+    public static final String IN_OPERATOR = '$in'
+    public static final String OR_OPERATOR = '$or'
+    public static final String AND_OPERATOR = '$and'
+    public static final String GTE_OPERATOR = '$gte'
+    public static final String LTE_OPERATOR = '$lte'
+    public static final String GT_OPERATOR = '$gt'
+    public static final String LT_OPERATOR = '$lt'
+    public static final String NE_OPERATOR = '$ne'
+    public static final String EQ_OPERATOR = '$eq'
+    public static final String NIN_OPERATOR = '$nin'
+    public static final String REGEX_OPERATOR = '$regex'
+    public static final String MATCH_OPERATOR = '$match'
+    public static final String AVERAGE_OPERATOR = '$avg'
+    public static final String GROUP_OPERATOR = '$group'
+    public static final String SUM_OPERATOR = '$sum'
+    public static final String MIN_OPERATOR = '$min'
+    public static final String MAX_OPERATOR = '$max'
+    public static final String SIZE_OPERATOR = '$size'
+    public static final String NOT_OPERATOR = '$not'
+    public static final String NOR_OPERATOR = '$nor'
+    public static final String EXISTS_OPERATOR = '$exists'
+    public static final String WHERE_OPERATOR = '$where'
+    public static final EncoderContext ENCODER_CONTEXT = EncoderContext.builder().build()
+    public static final String ID_REFERENCE_SUFFIX = '.$id'
+
+    private static final String THIS_PREFIX = 'this.'
+    protected static Map<Class, QueryHandler> queryHandlers = new HashMap<>()
+    protected static Map<String, OperatorHandler> operatorHandlers = new HashMap<>()
+    protected static Map<Class, ProjectionHandler> groupByProjectionHandlers = new HashMap<>()
+
+    protected static Map<Class, ProjectionHandler> projectProjectionHandlers = new HashMap<>()
+
+    static {
+        queryHandlers.put(IdEquals, new QueryHandler<IdEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, IdEquals criterion, Document query, PersistentEntity entity) {
+                Object value = criterion.getValue()
+                MappingContext mappingContext = entity.getMappingContext()
+                PersistentProperty identity = entity.getIdentity()
+                Object converted = mappingContext.getConversionService().convert(value, identity.getType())
+                Property mappedForm = (Property) identity.getMapping().getMappedForm()
+                String targetProperty = mappedForm.getTargetName()
+                if (targetProperty == null) {
+                    targetProperty = identity.getName()
+                }
+                query.put(targetProperty, converted)
+            }
+        })
+
+        queryHandlers.put(Equals, new QueryHandler<Equals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Equals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                PersistentProperty persistentProperty = entity.getPropertyByName(criterion.getProperty())
+                Object value
+                if ((persistentProperty instanceof Embedded) && criterion.getValue() != null) {
+                    value = queryEncoder.encode((Embedded) persistentProperty, criterion.getValue())
+                } else {
+                    value = criterion.getValue()
+                }
+                if (value instanceof Pattern) {
+                    Pattern pattern = (Pattern) value
+                    query.put(propertyName, new Document(REGEX_OPERATOR, pattern.toString()))
+                } else {
+                    query.put(propertyName, value)
+                }
+            }
+        })
+
+        queryHandlers.put(IsNull, new QueryHandler<IsNull>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, IsNull criterion, Document query, PersistentEntity entity) {
+                queryHandlers.get(Equals).handle(queryEncoder, new Equals(criterion.getProperty(), null), query, entity)
+            }
+        })
+        queryHandlers.put(IsNotNull, new QueryHandler<IsNotNull>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, IsNotNull criterion, Document query, PersistentEntity entity) {
+                queryHandlers.get(NotEquals).handle(queryEncoder, new NotEquals(criterion.getProperty(), null), query, entity)
+            }
+        })
+        queryHandlers.put(EqualsProperty, new QueryHandler<EqualsProperty>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, EqualsProperty criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty())
+                addWherePropertyComparison(query, propertyName, otherPropertyName, '==')
+            }
+        })
+        queryHandlers.put(NotEqualsProperty, new QueryHandler<NotEqualsProperty>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, NotEqualsProperty criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty())
+                addWherePropertyComparison(query, propertyName, otherPropertyName, '!=')
+            }
+        })
+        queryHandlers.put(GreaterThanProperty, new QueryHandler<GreaterThanProperty>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, GreaterThanProperty criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty())
+                addWherePropertyComparison(query, propertyName, otherPropertyName, '>')
+            }
+        })
+        queryHandlers.put(LessThanProperty, new QueryHandler<LessThanProperty>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, LessThanProperty criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty())
+                addWherePropertyComparison(query, propertyName, otherPropertyName, '<')
+            }
+        })
+        queryHandlers.put(GreaterThanEqualsProperty, new QueryHandler<GreaterThanEqualsProperty>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, GreaterThanEqualsProperty criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty())
+                addWherePropertyComparison(query, propertyName, otherPropertyName, '>=')
+            }
+        })
+        queryHandlers.put(LessThanEqualsProperty, new QueryHandler<LessThanEqualsProperty>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, LessThanEqualsProperty criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty())
+                addWherePropertyComparison(query, propertyName, otherPropertyName, '<=')
+            }
+        })
+
+        queryHandlers.put(NotEquals, new QueryHandler<NotEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, NotEquals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Document notEqualQuery = getOrCreatePropertyQuery(query, propertyName)
+                notEqualQuery.put(NE_OPERATOR, criterion.getValue())
+
+                query.put(propertyName, notEqualQuery)
+            }
+        })
+
+        queryHandlers.put(Like, new QueryHandler<Like>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Like like, Document query, PersistentEntity entity) {
+                handleLike(entity, like, query, true)
+            }
+        })
+
+        queryHandlers.put(ILike, new QueryHandler<ILike>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, ILike like, Document query, PersistentEntity entity) {
+                handleLike(entity, like, query, false)
+            }
+        })
+
+        queryHandlers.put(RLike, new QueryHandler<RLike>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, RLike like, Document query, PersistentEntity entity) {
+                Object value = like.getValue()
+                if (value == null) {
+                    value = 'null'
+                }
+                final String expr = value.toString()
+                Pattern regex = Pattern.compile(expr)
+                String propertyName = getPropertyName(entity, like)
+                query.put(propertyName, regex)
+            }
+        })
+
+        queryHandlers.put(In, new QueryHandler<In>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, In inCriterion, Document query, PersistentEntity entity) {
+                Document inQuery = new Document()
+                List values = getInListQueryValues(entity, inCriterion)
+                inQuery.put(IN_OPERATOR, values)
+                String propertyName = getPropertyName(entity, inCriterion)
+                query.put(propertyName, inQuery)
+            }
+        })
+
+        queryHandlers.put(Between, new QueryHandler<Between>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Between between, Document query, PersistentEntity entity) {
+                Document betweenQuery = new Document()
+                betweenQuery.put(GTE_OPERATOR, between.getFrom())
+                betweenQuery.put(LTE_OPERATOR, between.getTo())
+                String propertyName = getPropertyName(entity, between)
+                query.put(propertyName, betweenQuery)
+            }
+        })
+
+        queryHandlers.put(GreaterThan, new QueryHandler<GreaterThan>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, GreaterThan criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Document greaterThanQuery = getOrCreatePropertyQuery(query, propertyName)
+                greaterThanQuery.put(GT_OPERATOR, criterion.getValue())
+
+                query.put(propertyName, greaterThanQuery)
+            }
+        })
+
+        queryHandlers.put(GreaterThanEquals, new QueryHandler<GreaterThanEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, GreaterThanEquals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Document greaterThanQuery = getOrCreatePropertyQuery(query, propertyName)
+                greaterThanQuery.put(GTE_OPERATOR, criterion.getValue())
+
+                query.put(propertyName, greaterThanQuery)
+            }
+        })
+
+        queryHandlers.put(LessThan, new QueryHandler<LessThan>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, LessThan criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Document lessThanQuery = getOrCreatePropertyQuery(query, propertyName)
+                lessThanQuery.put(LT_OPERATOR, criterion.getValue())
+
+                query.put(propertyName, lessThanQuery)
+            }
+        })
+
+        queryHandlers.put(LessThanEquals, new QueryHandler<LessThanEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, LessThanEquals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Document lessThanQuery = getOrCreatePropertyQuery(query, propertyName)
+                lessThanQuery.put(LTE_OPERATOR, criterion.getValue())
+
+                query.put(propertyName, lessThanQuery)
+            }
+        })
+
+        queryHandlers.put(Conjunction, new QueryHandler<Conjunction>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Conjunction criterion, Document query, PersistentEntity entity) {
+                populateBsonQuery(queryEncoder, query, criterion, entity)
+            }
+        })
+
+        queryHandlers.put(Negation, new QueryHandler<Negation>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Negation criteria, Document query, PersistentEntity entity) {
+                List nor = new ArrayList()
+                query.put(NOR_OPERATOR, nor)
+                for (Criterion criterion : criteria.getCriteria()) {
+                    Document negatedQuery = new Document()
+                    nor.add(negatedQuery)
+                    if (criterion instanceof PropertyCriterion) {
+                        PropertyCriterion pc = (PropertyCriterion) criterion
+                        PersistentProperty property = entity.getPropertyByName(pc.getProperty())
+                        if (property instanceof Custom) {
+                            CustomTypeMarshaller<Object, Object, Document> customTypeMarshaller = (CustomTypeMarshaller<Object, Object, Document>) ((Custom) property).getCustomTypeMarshaller()
+                            customTypeMarshaller.query(property, pc, query)
+                            continue
+                        }
+                    }
+
+                    final QueryHandler queryHandler = queryHandlers.get(criterion.getClass())
+                    if (queryHandler != null) {
+                        queryHandler.handle(queryEncoder, criterion, negatedQuery, entity)
+                    } else {
+                        throw new UnsupportedOperationException('Query of type ' + criterion.getClass().getSimpleName() + ' cannot be negated')
+                    }
+                }
+            }
+        })
+
+        queryHandlers.put(Disjunction, new QueryHandler<Disjunction>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Disjunction criterion, Document query, PersistentEntity entity) {
+                populateBsonQuery(queryEncoder, query, criterion, entity)
+            }
+        })
+
+        queryHandlers.put(SizeEquals, new QueryHandler<SizeEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, SizeEquals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Document sizeEqualsQuery = getOrCreatePropertyQuery(query, propertyName)
+                sizeEqualsQuery.put(SIZE_OPERATOR, getNumber(criterion))
+
+                query.put(propertyName, sizeEqualsQuery)
+            }
+        })
+
+        queryHandlers.put(SizeNotEquals, new QueryHandler<SizeNotEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, SizeNotEquals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Document sizeNotEqualsQuery = getOrCreatePropertyQuery(query, propertyName)
+                sizeNotEqualsQuery.put(NOT_OPERATOR, new Document(SIZE_OPERATOR, getNumber(criterion)))
+
+                query.put(propertyName, sizeNotEqualsQuery)
+            }
+        })
+
+        queryHandlers.put(SizeGreaterThan, new QueryHandler<SizeGreaterThan>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, SizeGreaterThan criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Integer greaterThanValue = getNumber(criterion)
+
+                query.put(propertyName + '.' + greaterThanValue, new Document(EXISTS_OPERATOR, true))
+            }
+        })
+
+        queryHandlers.put(SizeLessThan, new QueryHandler<SizeLessThan>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, SizeLessThan criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Integer lessThanValue = getNumber(criterion)
+
+                query.put(propertyName + '.' + (lessThanValue - 1), new Document(EXISTS_OPERATOR, 0))
+            }
+        })
+
+        queryHandlers.put(SizeLessThanEquals, new QueryHandler<SizeLessThanEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, SizeLessThanEquals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Integer lessThanValue = getNumber(criterion)
+
+                query.put(propertyName + '.' + lessThanValue, new Document(EXISTS_OPERATOR, 0))
+            }
+        })
+
+        queryHandlers.put(SizeGreaterThanEquals, new QueryHandler<SizeGreaterThanEquals>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, SizeGreaterThanEquals criterion, Document query, PersistentEntity entity) {
+                String propertyName = getPropertyName(entity, criterion)
+                Integer greaterThanValue = getNumber(criterion)
+
+                query.put(propertyName + '.' + (greaterThanValue - 1), new Document(EXISTS_OPERATOR, true))
+            }
+        })
+
+        operatorHandlers.put(GT_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value != null && !(value instanceof NullObject)) {
+                    criteria.add(new Query.GreaterThan(attributeName, value))
+                }
+            }
+        })
+
+        operatorHandlers.put(GTE_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value != null && !(value instanceof NullObject)) {
+                    criteria.add(new Query.GreaterThanEquals(attributeName, value))
+                }
+            }
+        })
+
+        operatorHandlers.put(LT_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value != null && !(value instanceof NullObject)) {
+                    criteria.add(new Query.LessThan(attributeName, value))
+                }
+            }
+        })
+        operatorHandlers.put(LTE_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value != null && !(value instanceof NullObject)) {
+                    criteria.add(new Query.LessThanEquals(attributeName, value))
+                }
+            }
+        })
+        operatorHandlers.put(NE_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value != null && !(value instanceof NullObject)) {
+                    criteria.add(new Query.NotEquals(attributeName, value))
+                }
+            }
+        })
+        operatorHandlers.put(EQ_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value != null) {
+                    criteria.add(new Query.Equals(attributeName, value))
+                }
+            }
+        })
+
+        operatorHandlers.put(REGEX_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value != null && !(value instanceof NullObject)) {
+                    criteria.add(new Query.RLike(attributeName, value.toString()))
+                }
+            }
+        })
+
+        operatorHandlers.put(OR_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Disjunction disjunction = new Disjunction()
+                criteria.add(disjunction)
+
+                queryReader.readStartArray()
+                BsonType bsonType = queryReader.readBsonType()
+                parseJunctionDocuments(disjunction, attributeName, queryReader, bsonType)
+                queryReader.readEndArray()
+            }
+        })
+
+        operatorHandlers.put(IN_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                List values = readArrayOfValues(queryReader)
+                criteria.add(new Query.In(attributeName, values))
+            }
+        })
+
+        operatorHandlers.put(NIN_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Negation negation = new Negation()
+                List values = readArrayOfValues(queryReader)
+                negation.add(new Query.In(attributeName, values))
+                criteria.add(negation)
+            }
+        })
+
+        operatorHandlers.put(NOT_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Negation negation = new Negation()
+                criteria.add(negation)
+
+                queryReader.readStartArray()
+                BsonType bsonType = queryReader.readBsonType()
+                parseJunctionDocuments(negation, attributeName, queryReader, bsonType)
+                queryReader.readEndArray()
+            }
+        })
+
+        operatorHandlers.put(AND_OPERATOR, new OperatorHandler() {
+            @Override
+            void handle(Junction criteria, String attributeName, BsonReader queryReader) {
+                Conjunction conj = new Conjunction()
+                criteria.add(conj)
+
+                queryReader.readStartArray()
+                BsonType bsonType = queryReader.readBsonType()
+                parseJunctionDocuments(conj, attributeName, queryReader, bsonType)
+                queryReader.readEndArray()
+            }
+        })
+    }
+
+    @PackageScope
+    static List readArrayOfValues(BsonReader queryReader) {
+        List values = new ArrayList()
+        BsonType bsonType = queryReader.getCurrentBsonType()
+        if (bsonType == BsonType.ARRAY) {
+            queryReader.readStartArray()
+            bsonType = queryReader.readBsonType()
+            while (bsonType != BsonType.END_OF_DOCUMENT) {
+                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType())
+                if (value instanceof NullObject) {
+                    value = null
+                }
+                values.add(value)
+                bsonType = queryReader.readBsonType()
+            }
+
+            queryReader.readEndArray()
+        }
+        return values
+    }
+
+    @PackageScope
+    static void parseJunctionDocuments(Junction junction, String attributeName, BsonReader queryReader, BsonType bsonType) {
+        BsonType currentType = bsonType
+        while (currentType != BsonType.END_OF_DOCUMENT) {
+            if (currentType == BsonType.DOCUMENT) {
+                parseQueryAttributeValue(queryReader, currentType, attributeName, junction)
+            } else {
+                queryReader.skipValue()
+            }
+            currentType = queryReader.readBsonType()
+        }
+    }
+
+    protected BsonQuery(Session session, PersistentEntity entity) {
+        super(session, entity)
+    }
+
+    protected BsonQuery(PersistentEntity entity) {
+        super((Session) null, entity)
+    }
+
+    /**
+     * Creates a new query for the given registry, entity and criteria
+     *
+     * @param registry The registry
+     * @param entity   The entity
+     * @param criteria The criteria
+     * @return The query
+     */
+    static Document createBsonQuery(CodecRegistry registry, PersistentEntity entity, List<Criterion> criteria) {
+        Conjunction junction = new Conjunction(criteria)
+        return createBsonQuery(registry, entity, junction)
+    }
+
+    /**
+     * Creates a new query for the given registry, entity and criteria
+     *
+     * @param registry The registry
+     * @param entity   The entity
+     * @param junction The junction
+     * @return The query
+     */
+    static Document createBsonQuery(CodecRegistry registry, PersistentEntity entity, Junction junction) {
+        EmbeddedQueryEncoder embeddedQueryEncoder = new CodecRegistryEmbeddedQueryEncoder(registry)
+        Document query = new Document()
+        if (junction instanceof Conjunction) {
+            populateBsonQuery(embeddedQueryEncoder, query, junction.getCriteria(), entity)
+        } else {
+            populateBsonQuery(embeddedQueryEncoder, query, junction, entity)
+        }
+        return query
+    }
+
+    /**
+     * Parse a query from a BsonReader into a DetachedCriteria
+     *
+     * @param type        The entity type
+     * @param queryReader The query reader
+     * @param <T>         The entity concrete type
+     * @return A {@link DetachedCriteria}
+     */
+    static <T> DetachedCriteria<T> parse(Class<T> type, BsonReader queryReader) {
+        queryReader.readStartDocument()
+        BsonType bsonType = queryReader.getCurrentBsonType()
+        DetachedCriteria<T> criteria = new DetachedCriteria<T>(type)
+        Query.Junction junction = new Query.Conjunction()
+
+        if (bsonType != BsonType.END_OF_DOCUMENT) {
+            String attributeName = queryReader.readName()
+            boolean isJunction = false
+            switch (attributeName) {
+                case OR_OPERATOR:
+                    junction = new Disjunction()
+                    isJunction = true
+                    break
+                case NOT_OPERATOR:
+                    junction = new Negation()
+                    isJunction = true
+                    break
+                case AND_OPERATOR:
+                    junction = new Conjunction()
+                    isJunction = true
+                    break
+                default:
+                    parseQueryAttributeValue(queryReader, queryReader.getCurrentBsonType(), attributeName, junction)
+                    bsonType = queryReader.readBsonType()
+                    break
+            }
+
+            criteria.add(junction)
+
+            if (isJunction) {
+                queryReader.readStartArray()
+                bsonType = queryReader.readBsonType()
+                while (bsonType != BsonType.END_OF_DOCUMENT) {
+                    if (bsonType == BsonType.DOCUMENT) {
+                        parseQueryAttributeValue(queryReader, queryReader.getCurrentBsonType(), attributeName, junction)
+                    } else {
+                        queryReader.skipValue()
+                    }
+                    bsonType = queryReader.readBsonType()
+                }
+                queryReader.readEndArray()
+            } else {
+                while (bsonType != BsonType.END_OF_DOCUMENT) {
+                    attributeName = queryReader.readName()
+                    bsonType = queryReader.getCurrentBsonType()
+                    parseQueryAttributeValue(queryReader, bsonType, attributeName, junction)
+                    bsonType = queryReader.readBsonType()
+                }
+            }
+        }
+
+        return criteria
+    }
+
+    private static BsonType parseQueryAttributeValue(BsonReader queryReader, BsonType bsonType, String attributeName, Junction junction) {
+        BsonType currentType = bsonType
+        if (currentType == BsonType.DOCUMENT) {
+            queryReader.readStartDocument()
+            currentType = queryReader.getCurrentBsonType()
+            while (currentType != BsonType.END_OF_DOCUMENT) {
+                String operator = queryReader.readName()
+                OperatorHandler operatorHandler = operatorHandlers.get(operator)
+                if (operatorHandler != null) {
+                    operatorHandler.handle(junction, attributeName, queryReader)
+                } else {
+                    parseQueryAttributeValue(queryReader, queryReader.getCurrentBsonType(), operator, junction)
+                }
+                currentType = queryReader.readBsonType()
+            }
+            queryReader.readEndDocument()
+        } else {
+            Object value = readBsonValue(queryReader, currentType)
+            if (value != null) {
+                if (value instanceof NullObject) {
+                    junction.add(new IsNull(attributeName))
+                } else {
+                    junction.add(new Equals(attributeName, value))
+                }
+            }
+        }
+        return currentType
+    }
+
+    protected static Object readBsonValue(BsonReader queryReader, BsonType bsonType) {
+        Object value = null
+        switch (bsonType) {
+            case BsonType.STRING:
+                value = queryReader.readString()
+                break
+            case BsonType.INT32:
+                value = queryReader.readInt32()
+                break
+            case BsonType.INT64:
+                value = queryReader.readInt64()
+                break
+            case BsonType.BOOLEAN:
+                value = queryReader.readBoolean()
+                break
+            case BsonType.DOUBLE:
+                value = queryReader.readDouble()
+                break
+            case BsonType.BINARY:
+                value = queryReader.readBinaryData().getData()
+                break
+            case BsonType.REGULAR_EXPRESSION:
+                value = queryReader.readRegularExpression().getPattern()
+                break
+            case BsonType.DATE_TIME:
+                value = new Date(queryReader.readDateTime())
+                break
+            case BsonType.OBJECT_ID:
+                value = queryReader.readObjectId()
+                break
+            case BsonType.NULL:
+                value = NullObject.getNullObject()
+                break
+            default:
+                queryReader.skipValue()
+                break
+        }
+        return value
+    }
+
+    protected static String getPropertyName(PersistentEntity entity, PropertyNameCriterion criterion) {
+        String propertyName = criterion.getProperty()
+        return getPropertyName(entity, propertyName)
+    }
+
+    protected static String getPropertyName(PersistentEntity entity, String propertyName) {
+        String name = propertyName
+        if (entity.isIdentityName(name)) {
+            String targetIdentityName = ((Property) entity.getIdentity().getMapping().getMappedForm()).getTargetName()
+            if (targetIdentityName != null) {
+                name = targetIdentityName
+            }
+        } else {
+            PersistentProperty property = entity.getPropertyByName(name)
+            if (property != null) {
+                name = MappingUtils.getTargetKey(property)
+                if (property instanceof ToOne && !(property instanceof Embedded)) {
+                    ToOne association = (ToOne) property
+                    Property attr = (Property) association.getMapping().getMappedForm()
+                    boolean isReference = attr == null || attr.isReference()
+                    if (isReference) {
+                        name = name + ID_REFERENCE_SUFFIX
+                    }
+                }
+            }
+        }
+
+        return name
+    }
+
+    @PackageScope
+    static void addWherePropertyComparison(Document query, String propertyName, String otherPropertyName, String operator) {
+        query.put(WHERE_OPERATOR, new StringBuilder(THIS_PREFIX).append(propertyName).append(operator).append(THIS_PREFIX).append(otherPropertyName).toString())
+    }
+
+    @PackageScope
+    static void handleLike(PersistentEntity entity, Like like, Document query, boolean caseSensitive) {
+        Object value = like.getValue()
+        String expr = patternToRegex(value)
+
+        Pattern regex = caseSensitive ? Pattern.compile(expr) : Pattern.compile(expr, Pattern.CASE_INSENSITIVE)
+        String propertyName = getPropertyName(entity, like)
+        query.put(propertyName, regex)
+    }
+
+    /**
+     * Get the list of native values to use in the query. This converts entities to ids and other types to
+     * their persisted types.
+     *
+     * @param entity The entity
+     * @param inCriterion The criterion
+     * @return The list of native values suitable for passing to Mongo.
+     */
+    protected static List<Object> getInListQueryValues(PersistentEntity entity, In inCriterion) {
+        List<Object> values = new ArrayList<Object>(inCriterion.getValues().size())
+        final MappingContext mappingContext = entity.getMappingContext()
+        for (Object value : inCriterion.getValues()) {
+            if (mappingContext.isPersistentEntity(value)) {
+                PersistentEntity pe = mappingContext.getPersistentEntity(
+                        value.getClass().getName())
+                ProxyHandler proxyHandler = mappingContext.getProxyHandler()
+                if (proxyHandler.isProxy(value)) {
+                    values.add(proxyHandler.getIdentifier(value))
+                } else {
+                    EntityReflector reflector = mappingContext.getEntityReflector(pe)
+                    values.add(reflector.getIdentifier(value))
+                }
+            } else {
+                values.add(value)
+            }
+        }
+        return values
+    }
+
+    protected static Document getOrCreatePropertyQuery(Document query, String propertyName) {
+        Object existing = query.get(propertyName)
+        Document queryObject = existing instanceof Document ? (Document) existing : null
+        if (queryObject == null) {
+            queryObject = new Document()
+        }
+        return queryObject
+    }
+
+    @PackageScope
+    static Integer getNumber(PropertyCriterion criterion) {
+        Object value = criterion.getValue()
+        if (value instanceof Number) {
+            return ((Number) value).intValue()
+        }
+        throw new IllegalArgumentException('Argument to size constraint must be a number')
+    }
+
+    /**
+     * Handles an individual criterion
+     *
+     * @param <T>
+     */
+    protected interface QueryHandler<T> {
+
+        void handle(EmbeddedQueryEncoder queryEncoder, T criterion, Document query, PersistentEntity entity)
+
+    }
+
+    /**
+     * Handles query operators when reading BSON
+     */
+    protected interface OperatorHandler {
+
+        void handle(Junction criteria, String attributeName, BsonReader queryReader)
+
+    }
+
+    /**
+     *
+     * Handles a projection
+     *
+     * @param <T>
+     */
+    protected interface ProjectionHandler<T extends Projection> {
+
+        /**
+         * Handles a projection modifying the aggregation pipeline appropriately
+         *
+         * @param entity        The entity
+         * @param groupByObject The group by object
+         * @param projection    The projection
+         * @return The key to be used to obtain the projected value from the pipeline results
+         */
+        String handle(PersistentEntity entity, Document projectObject, Document groupByObject, T projection)
+
+    }
+
+    protected static void populateBsonQuery(final EmbeddedQueryEncoder queryEncoder, Document query, List<Criterion> criteria, final PersistentEntity entity) {
+        // if a query combines more than 1 item, wrap the items in individual $and or $or arguments
+        // so that property names can't clash (e.g. for an $and containing two $ors)
+        for (Criterion criterion : criteria) {
+            final QueryHandler queryHandler = queryHandlers.get(criterion.getClass())
+            if (queryHandler != null) {
+                Document dbo = query
+                if (criterion instanceof PropertyCriterion) {
+                    PropertyCriterion pc = (PropertyCriterion) criterion
+                    PersistentProperty property = entity.getPropertyByName(pc.getProperty())
+                    if (property instanceof Custom) {
+                        CustomTypeMarshaller<Object, Object, Document> customTypeMarshaller = (CustomTypeMarshaller<Object, Object, Document>) ((Custom) property).getCustomTypeMarshaller()
+                        if (!(customTypeMarshaller instanceof CodecCustomTypeMarshaller)) {
+                            customTypeMarshaller.query(property, pc, query)
+                            continue
+                        }
+                    }
+                }
+                queryHandler.handle(queryEncoder, criterion, dbo, entity)
+            } else {
+                throw new InvalidDataAccessResourceUsageException('Queries of type ' + criterion.getClass().getSimpleName() + ' are not supported by this implementation')
+            }
+        }
+    }
+
+    protected static void populateBsonQuery(final EmbeddedQueryEncoder queryEncoder, Document query, Junction criteria, final PersistentEntity entity) {
+        List subList = null
+        if (criteria instanceof Disjunction) {
+            subList = new ArrayList()
+            query.put(OR_OPERATOR, subList)
+        } else if (criteria instanceof Conjunction) {
+            subList = new ArrayList()
+            query.put(AND_OPERATOR, subList)
+        } else if (criteria instanceof Negation) {
+            subList = new ArrayList()
+            query.put(NOT_OPERATOR, new Document(OR_OPERATOR, subList))
+        }
+
+        for (Criterion criterion : criteria.getCriteria()) {
+            final QueryHandler queryHandler = queryHandlers.get(criterion.getClass())
+            if (queryHandler != null) {
+                Document dbo = query
+                if (subList != null) {
+                    dbo = new Document()
+                    subList.add(dbo)
+                }
+
+                if (criterion instanceof PropertyCriterion) {
+                    PropertyCriterion pc = (PropertyCriterion) criterion
+                    PersistentProperty property = entity.getPropertyByName(pc.getProperty())
+                    if (property instanceof Custom) {
+                        CustomTypeMarshaller<Object, Object, Document> customTypeMarshaller = (CustomTypeMarshaller<Object, Object, Document>) ((Custom) property).getCustomTypeMarshaller()
+                        customTypeMarshaller.query(property, pc, query)
+                        continue
+                    }
+                }
+                queryHandler.handle(queryEncoder, criterion, dbo, entity)
+            } else {
+                throw new InvalidDataAccessResourceUsageException('Queries of type ' + criterion.getClass().getSimpleName() + ' are not supported by this implementation')
+            }
+        }
+    }
+
+}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.java b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.java
deleted file mode 100644
index d59c991e44..0000000000
--- a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/query/BsonQuery.java
+++ /dev/null
@@ -1,901 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-
-package org.grails.datastore.bson.query;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-import org.codehaus.groovy.runtime.NullObject;
-
-import org.bson.BsonReader;
-import org.bson.BsonType;
-import org.bson.Document;
-import org.bson.codecs.EncoderContext;
-import org.bson.codecs.configuration.CodecRegistry;
-
-import org.springframework.dao.InvalidDataAccessResourceUsageException;
-
-import grails.gorm.DetachedCriteria;
-import org.grails.datastore.bson.codecs.CodecCustomTypeMarshaller;
-import org.grails.datastore.mapping.config.Property;
-import org.grails.datastore.mapping.core.Session;
-import org.grails.datastore.mapping.engine.internal.MappingUtils;
-import org.grails.datastore.mapping.engine.types.CustomTypeMarshaller;
-import org.grails.datastore.mapping.model.MappingContext;
-import org.grails.datastore.mapping.model.PersistentEntity;
-import org.grails.datastore.mapping.model.PersistentProperty;
-import org.grails.datastore.mapping.model.types.Custom;
-import org.grails.datastore.mapping.model.types.Embedded;
-import org.grails.datastore.mapping.model.types.ToOne;
-import org.grails.datastore.mapping.proxy.ProxyHandler;
-import org.grails.datastore.mapping.query.Query;
-import org.grails.datastore.mapping.reflect.EntityReflector;
-
-/**
- * A base class for Query implementations that create BSON queries based on MongoDB query format. See https://docs.mongodb.com/manual/tutorial/query-documents/
- *
- * @author Graeme Rocher
- * @since 6.0
- */
-public abstract class BsonQuery extends Query {
-    public static final String PROJECT_OPERATOR = "$project";
-    public static final String SORT_OPERATOR = "$sort";
-    public static final String IN_OPERATOR = "$in";
-    public static final String OR_OPERATOR = "$or";
-    public static final String AND_OPERATOR = "$and";
-    public static final String GTE_OPERATOR = "$gte";
-    public static final String LTE_OPERATOR = "$lte";
-    public static final String GT_OPERATOR = "$gt";
-    public static final String LT_OPERATOR = "$lt";
-    public static final String NE_OPERATOR = "$ne";
-    public static final String EQ_OPERATOR = "$eq";
-    public static final String NIN_OPERATOR = "$nin";
-    public static final String REGEX_OPERATOR = "$regex";
-    public static final String MATCH_OPERATOR = "$match";
-    public static final String AVERAGE_OPERATOR = "$avg";
-    public static final String GROUP_OPERATOR = "$group";
-    public static final String SUM_OPERATOR = "$sum";
-    public static final String MIN_OPERATOR = "$min";
-    public static final String MAX_OPERATOR = "$max";
-    public static final String SIZE_OPERATOR = "$size";
-    public static final String NOT_OPERATOR = "$not";
-    public static final String NOR_OPERATOR = "$nor";
-    public static final String EXISTS_OPERATOR = "$exists";
-    public static final String WHERE_OPERATOR = "$where";
-    public static final EncoderContext ENCODER_CONTEXT = EncoderContext.builder().build();
-    public static final String ID_REFERENCE_SUFFIX = ".$id";
-
-    private static final String THIS_PREFIX = "this.";
-    protected static Map<Class, QueryHandler> queryHandlers = new HashMap<>();
-    protected static Map<String, OperatorHandler> operatorHandlers = new HashMap<>();
-    protected static Map<Class, ProjectionHandler> groupByProjectionHandlers = new HashMap<>();
-
-    protected static Map<Class, ProjectionHandler> projectProjectionHandlers = new HashMap<>();
-
-    static {
-        queryHandlers.put(IdEquals.class, new QueryHandler<IdEquals>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, IdEquals criterion, Document query, PersistentEntity entity) {
-                Object value = criterion.getValue();
-                MappingContext mappingContext = entity.getMappingContext();
-                PersistentProperty identity = entity.getIdentity();
-                Object converted = mappingContext.getConversionService().convert(value, identity.getType());
-                Property mappedForm = identity.getMapping().getMappedForm();
-                String targetProperty = mappedForm.getTargetName();
-                if (targetProperty == null) {
-                    targetProperty = identity.getName();
-                }
-                query.put(targetProperty, converted);
-            }
-        });
-
-        queryHandlers.put(Equals.class, new QueryHandler<Equals>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, Equals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                PersistentProperty persistentProperty = entity.getPropertyByName(criterion.getProperty());
-                Object value;
-                if ((persistentProperty instanceof Embedded) && criterion.getValue() != null) {
-                    value = queryEncoder.encode((Embedded) persistentProperty, criterion.getValue());
-                } else {
-                    value = criterion.getValue();
-                }
-                if (value instanceof Pattern) {
-                    Pattern pattern = (Pattern) value;
-                    query.put(propertyName, new Document(REGEX_OPERATOR, pattern.toString()));
-                } else {
-                    query.put(propertyName, value);
-                }
-            }
-        });
-
-        queryHandlers.put(IsNull.class, new QueryHandler<IsNull>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, IsNull criterion, Document query, PersistentEntity entity) {
-                queryHandlers.get(Equals.class).handle(queryEncoder, new Equals(criterion.getProperty(), null), query, entity);
-            }
-        });
-        queryHandlers.put(IsNotNull.class, new QueryHandler<IsNotNull>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, IsNotNull criterion, Document query, PersistentEntity entity) {
-                queryHandlers.get(NotEquals.class).handle(queryEncoder, new NotEquals(criterion.getProperty(), null), query, entity);
-            }
-        });
-        queryHandlers.put(EqualsProperty.class, new QueryHandler<EqualsProperty>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, EqualsProperty criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty());
-                addWherePropertyComparison(query, propertyName, otherPropertyName, "==");
-            }
-        });
-        queryHandlers.put(NotEqualsProperty.class, new QueryHandler<NotEqualsProperty>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, NotEqualsProperty criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty());
-                addWherePropertyComparison(query, propertyName, otherPropertyName, "!=");
-            }
-        });
-        queryHandlers.put(GreaterThanProperty.class, new QueryHandler<GreaterThanProperty>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, GreaterThanProperty criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty());
-                addWherePropertyComparison(query, propertyName, otherPropertyName, ">");
-            }
-        });
-        queryHandlers.put(LessThanProperty.class, new QueryHandler<LessThanProperty>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, LessThanProperty criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty());
-                addWherePropertyComparison(query, propertyName, otherPropertyName, "<");
-            }
-        });
-        queryHandlers.put(GreaterThanEqualsProperty.class, new QueryHandler<GreaterThanEqualsProperty>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, GreaterThanEqualsProperty criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty());
-                addWherePropertyComparison(query, propertyName, otherPropertyName, ">=");
-            }
-        });
-        queryHandlers.put(LessThanEqualsProperty.class, new QueryHandler<LessThanEqualsProperty>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, LessThanEqualsProperty criterion, Document query, PersistentEntity entity) {
-
-                String propertyName = getPropertyName(entity, criterion);
-                String otherPropertyName = getPropertyName(entity, criterion.getOtherProperty());
-                addWherePropertyComparison(query, propertyName, otherPropertyName, "<=");
-            }
-        });
-
-        queryHandlers.put(NotEquals.class, new QueryHandler<NotEquals>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, NotEquals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Document notEqualQuery = getOrCreatePropertyQuery(query, propertyName);
-                notEqualQuery.put(NE_OPERATOR, criterion.getValue());
-
-                query.put(propertyName, notEqualQuery);
-            }
-        });
-
-        queryHandlers.put(Like.class, new QueryHandler<Like>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, Like like, Document query, PersistentEntity entity) {
-                handleLike(entity, like, query, true);
-            }
-        });
-
-        queryHandlers.put(ILike.class, new QueryHandler<ILike>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, ILike like, Document query, PersistentEntity entity) {
-                handleLike(entity, like, query, false);
-            }
-        });
-
-        queryHandlers.put(RLike.class, new QueryHandler<RLike>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, RLike like, Document query, PersistentEntity entity) {
-                Object value = like.getValue();
-                if (value == null) value = "null";
-                final String expr = value.toString();
-                Pattern regex = Pattern.compile(expr);
-                String propertyName = getPropertyName(entity, like);
-                query.put(propertyName, regex);
-            }
-        });
-
-        queryHandlers.put(In.class, new QueryHandler<In>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, In in, Document query, PersistentEntity entity) {
-                Document inQuery = new Document();
-                List values = getInListQueryValues(entity, in);
-                inQuery.put(IN_OPERATOR, values);
-                String propertyName = getPropertyName(entity, in);
-                query.put(propertyName, inQuery);
-            }
-        });
-
-        queryHandlers.put(Between.class, new QueryHandler<Between>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, Between between, Document query, PersistentEntity entity) {
-                Document betweenQuery = new Document();
-                betweenQuery.put(GTE_OPERATOR, between.getFrom());
-                betweenQuery.put(LTE_OPERATOR, between.getTo());
-                String propertyName = getPropertyName(entity, between);
-                query.put(propertyName, betweenQuery);
-            }
-        });
-
-        queryHandlers.put(GreaterThan.class, new QueryHandler<GreaterThan>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, GreaterThan criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Document greaterThanQuery = getOrCreatePropertyQuery(query, propertyName);
-                greaterThanQuery.put(GT_OPERATOR, criterion.getValue());
-
-                query.put(propertyName, greaterThanQuery);
-            }
-        });
-
-        queryHandlers.put(GreaterThanEquals.class, new QueryHandler<GreaterThanEquals>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, GreaterThanEquals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Document greaterThanQuery = getOrCreatePropertyQuery(query, propertyName);
-                greaterThanQuery.put(GTE_OPERATOR, criterion.getValue());
-
-                query.put(propertyName, greaterThanQuery);
-            }
-        });
-
-        queryHandlers.put(LessThan.class, new QueryHandler<LessThan>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, LessThan criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Document lessThanQuery = getOrCreatePropertyQuery(query, propertyName);
-                lessThanQuery.put(LT_OPERATOR, criterion.getValue());
-
-                query.put(propertyName, lessThanQuery);
-            }
-        });
-
-        queryHandlers.put(LessThanEquals.class, new QueryHandler<LessThanEquals>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, LessThanEquals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Document lessThanQuery = getOrCreatePropertyQuery(query, propertyName);
-                lessThanQuery.put(LTE_OPERATOR, criterion.getValue());
-
-                query.put(propertyName, lessThanQuery);
-            }
-        });
-
-        queryHandlers.put(Conjunction.class, new QueryHandler<Conjunction>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, Conjunction criterion, Document query, PersistentEntity entity) {
-                populateBsonQuery(queryEncoder, query, criterion, entity);
-            }
-        });
-
-        queryHandlers.put(Negation.class, new QueryHandler<Negation>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, Negation criteria, Document query, PersistentEntity entity) {
-                List nor = new ArrayList();
-                query.put(NOR_OPERATOR, nor);
-                for (Criterion criterion : criteria.getCriteria()) {
-                    Document negatedQuery = new Document();
-                    nor.add(negatedQuery);
-                    if (criterion instanceof PropertyCriterion) {
-                        PropertyCriterion pc = (PropertyCriterion) criterion;
-                        PersistentProperty property = entity.getPropertyByName(pc.getProperty());
-                        if (property instanceof Custom) {
-                            CustomTypeMarshaller customTypeMarshaller = ((Custom) property).getCustomTypeMarshaller();
-                            customTypeMarshaller.query(property, pc, query);
-                            continue;
-                        }
-                    }
-
-                    final QueryHandler queryHandler = queryHandlers.get(criterion.getClass());
-                    if (queryHandler != null) {
-                        queryHandler.handle(queryEncoder, criterion, negatedQuery, entity);
-                    } else {
-                        throw new UnsupportedOperationException("Query of type " + criterion.getClass().getSimpleName() + " cannot be negated");
-                    }
-                }
-            }
-        });
-
-        queryHandlers.put(Disjunction.class, new QueryHandler<Disjunction>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, Disjunction criterion, Document query, PersistentEntity entity) {
-                populateBsonQuery(queryEncoder, query, criterion, entity);
-            }
-        });
-
-        queryHandlers.put(SizeEquals.class, new QueryHandler<SizeEquals>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, SizeEquals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Document sizeEqualsQuery = getOrCreatePropertyQuery(query, propertyName);
-                sizeEqualsQuery.put(SIZE_OPERATOR, getNumber(criterion));
-
-                query.put(propertyName, sizeEqualsQuery);
-            }
-        });
-
-        queryHandlers.put(SizeNotEquals.class, new QueryHandler<SizeNotEquals>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, SizeNotEquals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Document sizeNotEqualsQuery = getOrCreatePropertyQuery(query, propertyName);
-                sizeNotEqualsQuery.put(NOT_OPERATOR, new Document(SIZE_OPERATOR, getNumber(criterion)));
-
-                query.put(propertyName, sizeNotEqualsQuery);
-            }
-        });
-
-        queryHandlers.put(SizeGreaterThan.class, new QueryHandler<SizeGreaterThan>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, SizeGreaterThan criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Integer greaterThanValue = getNumber(criterion);
-
-                query.put(propertyName + '.' + greaterThanValue, new Document(EXISTS_OPERATOR, true));
-            }
-        });
-
-        queryHandlers.put(SizeLessThan.class, new QueryHandler<SizeLessThan>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, SizeLessThan criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Integer lessThanValue = getNumber(criterion);
-
-                query.put(propertyName + '.' + (lessThanValue - 1), new Document(EXISTS_OPERATOR, 0));
-            }
-        });
-
-        queryHandlers.put(SizeLessThanEquals.class, new QueryHandler<SizeLessThanEquals>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, SizeLessThanEquals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Integer lessThanValue = getNumber(criterion);
-
-                query.put(propertyName + '.' + lessThanValue, new Document(EXISTS_OPERATOR, 0));
-            }
-        });
-
-        queryHandlers.put(SizeGreaterThanEquals.class, new QueryHandler<SizeGreaterThanEquals>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, SizeGreaterThanEquals criterion, Document query, PersistentEntity entity) {
-                String propertyName = getPropertyName(entity, criterion);
-                Integer greaterThanValue = getNumber(criterion);
-
-                query.put(propertyName + '.' + (greaterThanValue - 1), new Document(EXISTS_OPERATOR, true));
-            }
-        });
-
-        operatorHandlers.put(GT_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value != null && !(value instanceof NullObject)) {
-                    criteria.add(new Query.GreaterThan(attributeName, value));
-                }
-            }
-        });
-
-        operatorHandlers.put(GTE_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value != null && !(value instanceof NullObject)) {
-                    criteria.add(new Query.GreaterThanEquals(attributeName, value));
-                }
-            }
-        });
-
-        operatorHandlers.put(LT_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value != null && !(value instanceof NullObject)) {
-                    criteria.add(new Query.LessThan(attributeName, value));
-                }
-            }
-        });
-        operatorHandlers.put(LTE_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value != null && !(value instanceof NullObject)) {
-                    criteria.add(new Query.LessThanEquals(attributeName, value));
-                }
-            }
-        });
-        operatorHandlers.put(NE_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value != null && !(value instanceof NullObject)) {
-                    criteria.add(new Query.NotEquals(attributeName, value));
-                }
-            }
-        });
-        operatorHandlers.put(EQ_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value != null) {
-                    criteria.add(new Query.Equals(attributeName, value));
-                }
-            }
-        });
-
-        operatorHandlers.put(REGEX_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value != null && !(value instanceof NullObject)) {
-                    criteria.add(new Query.RLike(attributeName, value.toString()));
-                }
-            }
-        });
-
-        operatorHandlers.put(OR_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Disjunction disjunction = new Disjunction();
-                criteria.add(disjunction);
-
-                queryReader.readStartArray();
-                BsonType bsonType = queryReader.readBsonType();
-                parseJunctionDocuments(disjunction, attributeName, queryReader, bsonType);
-                queryReader.readEndArray();
-            }
-        });
-
-        operatorHandlers.put(IN_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                List values = readArrayOfValues(queryReader);
-                criteria.add(new Query.In(attributeName, values));
-            }
-        });
-
-        operatorHandlers.put(NIN_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Negation negation = new Negation();
-                List values = readArrayOfValues(queryReader);
-                negation.add(new Query.In(attributeName, values));
-                criteria.add(negation);
-            }
-        });
-
-        operatorHandlers.put(NOT_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Negation negation = new Negation();
-                criteria.add(negation);
-
-                queryReader.readStartArray();
-                BsonType bsonType = queryReader.readBsonType();
-                parseJunctionDocuments(negation, attributeName, queryReader, bsonType);
-                queryReader.readEndArray();
-            }
-        });
-
-        operatorHandlers.put(AND_OPERATOR, new OperatorHandler() {
-            @Override
-            public void handle(Junction criteria, String attributeName, BsonReader queryReader) {
-                Conjunction conj = new Conjunction();
-                criteria.add(conj);
-
-                queryReader.readStartArray();
-                BsonType bsonType = queryReader.readBsonType();
-                parseJunctionDocuments(conj, attributeName, queryReader, bsonType);
-                queryReader.readEndArray();
-            }
-        });
-    }
-
-    private static List readArrayOfValues(BsonReader queryReader) {
-        List values = new ArrayList();
-        BsonType bsonType = queryReader.getCurrentBsonType();
-        if (bsonType == BsonType.ARRAY) {
-            queryReader.readStartArray();
-            bsonType = queryReader.readBsonType();
-            while (bsonType != BsonType.END_OF_DOCUMENT) {
-                Object value = readBsonValue(queryReader, queryReader.getCurrentBsonType());
-                if (value instanceof NullObject) {
-                    value = null;
-                }
-                values.add(value);
-                bsonType = queryReader.readBsonType();
-            }
-
-            queryReader.readEndArray();
-        }
-        return values;
-    }
-
-    private static void parseJunctionDocuments(Junction junction, String attributeName, BsonReader queryReader, BsonType bsonType) {
-        while (bsonType != BsonType.END_OF_DOCUMENT) {
-            if (bsonType == BsonType.DOCUMENT) {
-                parseQueryAttributeValue(queryReader, bsonType, attributeName, junction);
-            }
-            else {
-                queryReader.skipValue();
-            }
-            bsonType = queryReader.readBsonType();
-        }
-    }
-
-    protected BsonQuery(Session session, PersistentEntity entity) {
-        super(session, entity);
-    }
-
-    protected BsonQuery(PersistentEntity entity) {
-        super(null, entity);
-    }
-
-    /**
-     * Creates a new query for the given registry, entity and criteria
-     *
-     * @param registry The registry
-     * @param entity   The entity
-     * @param criteria The criteria
-     * @return The query
-     */
-    public static Document createBsonQuery(CodecRegistry registry, PersistentEntity entity, List<Criterion> criteria) {
-        Conjunction junction = new Conjunction(criteria);
-        return createBsonQuery(registry, entity, junction);
-    }
-
-    /**
-     * Creates a new query for the given registry, entity and criteria
-     *
-     * @param registry The registry
-     * @param entity   The entity
-     * @param junction The junction
-     * @return The query
-     */
-    public static Document createBsonQuery(CodecRegistry registry, PersistentEntity entity, Junction junction) {
-        EmbeddedQueryEncoder embeddedQueryEncoder = new CodecRegistryEmbeddedQueryEncoder(registry);
-        Document query = new Document();
-        if (junction instanceof Conjunction) {
-            populateBsonQuery(embeddedQueryEncoder, query, junction.getCriteria(), entity);
-        }
-        else {
-            populateBsonQuery(embeddedQueryEncoder, query, junction, entity);
-        }
-        return query;
-    }
-
-    /**
-     * Parse a query from a BsonReader into a DetachedCriteria
-     *
-     * @param type        The entity type
-     * @param queryReader The query reader
-     * @param <T>         The entity concrete type
-     * @return A {@link DetachedCriteria}
-     */
-    public static <T> DetachedCriteria<T> parse(Class<T> type, BsonReader queryReader) {
-        queryReader.readStartDocument();
-        BsonType bsonType = queryReader.getCurrentBsonType();
-        DetachedCriteria<T> criteria = new DetachedCriteria<>(type);
-        Query.Junction junction = new Query.Conjunction();
-
-        if (bsonType != BsonType.END_OF_DOCUMENT) {
-            String attributeName = queryReader.readName();
-            boolean isJunction = false;
-            switch (attributeName) {
-                case OR_OPERATOR:
-                    junction = new Disjunction();
-                    isJunction = true;
-                    break;
-                case NOT_OPERATOR:
-                    junction = new Negation();
-                    isJunction = true;
-                    break;
-                case AND_OPERATOR:
-                    junction = new Conjunction();
-                    isJunction = true;
-                    break;
-                default:
-                    parseQueryAttributeValue(queryReader, queryReader.getCurrentBsonType(), attributeName, junction);
-                    bsonType = queryReader.readBsonType();
-                    break;
-            }
-
-            criteria.add(junction);
-
-            if (isJunction) {
-                queryReader.readStartArray();
-                bsonType = queryReader.readBsonType();
-                while (bsonType != BsonType.END_OF_DOCUMENT) {
-                    if (bsonType == BsonType.DOCUMENT) {
-                        parseQueryAttributeValue(queryReader, queryReader.getCurrentBsonType(), attributeName, junction);
-                    }
-                    else {
-                        queryReader.skipValue();
-                    }
-                    bsonType = queryReader.readBsonType();
-                }
-                queryReader.readEndArray();
-            }
-            else {
-                while (bsonType != BsonType.END_OF_DOCUMENT) {
-                    attributeName = queryReader.readName();
-                    bsonType = queryReader.getCurrentBsonType();
-                    parseQueryAttributeValue(queryReader, bsonType, attributeName, junction);
-                    bsonType = queryReader.readBsonType();
-                }
-            }
-
-        }
-
-        return criteria;
-    }
-
-    private static BsonType parseQueryAttributeValue(BsonReader queryReader, BsonType bsonType, String attributeName, Junction junction) {
-        if (bsonType == BsonType.DOCUMENT) {
-            queryReader.readStartDocument();
-            bsonType = queryReader.getCurrentBsonType();
-            while (bsonType != BsonType.END_OF_DOCUMENT) {
-                String operator = queryReader.readName();
-                OperatorHandler operatorHandler = operatorHandlers.get(operator);
-                if (operatorHandler != null) {
-                    operatorHandler.handle(junction, attributeName, queryReader);
-                }
-                else {
-                    parseQueryAttributeValue(queryReader, queryReader.getCurrentBsonType(), operator, junction);
-                }
-                bsonType = queryReader.readBsonType();
-            }
-            queryReader.readEndDocument();
-        } else {
-            Object value = readBsonValue(queryReader, bsonType);
-            if (value != null) {
-                if (value instanceof NullObject) {
-                    junction.add(new IsNull(attributeName));
-                }
-                else {
-                    junction.add(new Equals(attributeName, value));
-                }
-            }
-        }
-        return bsonType;
-    }
-
-    protected static Object readBsonValue(BsonReader queryReader, BsonType bsonType) {
-        Object value = null;
-        switch (bsonType) {
-            case STRING:
-                value = queryReader.readString();
-                break;
-            case INT32:
-                value = queryReader.readInt32();
-                break;
-            case INT64:
-                value = queryReader.readInt64();
-                break;
-            case BOOLEAN:
-                value = queryReader.readBoolean();
-                break;
-            case DOUBLE:
-                value = queryReader.readDouble();
-                break;
-            case BINARY:
-                value = queryReader.readBinaryData().getData();
-                break;
-            case REGULAR_EXPRESSION:
-                value = queryReader.readRegularExpression().getPattern();
-                break;
-            case DATE_TIME:
-                value = new Date(queryReader.readDateTime());
-                break;
-            case OBJECT_ID:
-                value = queryReader.readObjectId();
-                break;
-            case NULL:
-                value = NullObject.getNullObject();
-                break;
-            default:
-                queryReader.skipValue();
-
-        }
-        return value;
-    }
-
-    protected static String getPropertyName(PersistentEntity entity, PropertyNameCriterion criterion) {
-        String propertyName = criterion.getProperty();
-        return getPropertyName(entity, propertyName);
-    }
-
-    protected static String getPropertyName(PersistentEntity entity, String propertyName) {
-        if (entity.isIdentityName(propertyName)) {
-            String targetIdentityName = entity.getIdentity().getMapping().getMappedForm().getTargetName();
-            if (targetIdentityName != null) {
-                propertyName = targetIdentityName;
-            }
-        } else {
-            PersistentProperty property = entity.getPropertyByName(propertyName);
-            if (property != null) {
-                propertyName = MappingUtils.getTargetKey(property);
-                if (property instanceof ToOne && !(property instanceof Embedded)) {
-                    ToOne association = (ToOne) property;
-                    Property attr = association.getMapping().getMappedForm();
-                    boolean isReference = attr == null || attr.isReference();
-                    if (isReference) {
-                        propertyName = propertyName + ID_REFERENCE_SUFFIX;
-                    }
-                }
-            }
-        }
-
-        return propertyName;
-    }
-
-    private static void addWherePropertyComparison(Document query, String propertyName, String otherPropertyName, String operator) {
-        query.put(WHERE_OPERATOR, new StringBuilder(THIS_PREFIX).append(propertyName).append(operator).append(THIS_PREFIX).append(otherPropertyName).toString());
-    }
-
-    private static void handleLike(PersistentEntity entity, Like like, Document query, boolean caseSensitive) {
-        Object value = like.getValue();
-        String expr = patternToRegex(value);
-
-        Pattern regex = caseSensitive ? Pattern.compile(expr) : Pattern.compile(expr, Pattern.CASE_INSENSITIVE);
-        String propertyName = getPropertyName(entity, like);
-        query.put(propertyName, regex);
-    }
-
-    /**
-     * Get the list of native values to use in the query. This converts entities to ids and other types to
-     * their persisted types.
-     *
-     * @param entity The entity
-     * @param in     The criterion
-     * @return The list of native values suitable for passing to Mongo.
-     */
-    protected static List<Object> getInListQueryValues(PersistentEntity entity, In in) {
-        List<Object> values = new ArrayList<>(in.getValues().size());
-        final MappingContext mappingContext = entity.getMappingContext();
-        for (Object value : in.getValues()) {
-            if (mappingContext.isPersistentEntity(value)) {
-                PersistentEntity pe = mappingContext.getPersistentEntity(
-                        value.getClass().getName());
-                ProxyHandler proxyHandler = mappingContext.getProxyHandler();
-                if (proxyHandler.isProxy(value)) {
-                    values.add(proxyHandler.getIdentifier(value));
-                }
-                else {
-                    EntityReflector reflector = mappingContext.getEntityReflector(pe);
-                    values.add(reflector.getIdentifier(value));
-                }
-            } else {
-                values.add(value);
-            }
-        }
-        return values;
-    }
-
-    protected static Document getOrCreatePropertyQuery(Document query, String propertyName) {
-        Object existing = query.get(propertyName);
-        Document queryObject = existing instanceof Document ? (Document) existing : null;
-        if (queryObject == null) {
-            queryObject = new Document();
-        }
-        return queryObject;
-    }
-
-    private static Integer getNumber(PropertyCriterion criterion) {
-        Object value = criterion.getValue();
-        if (value instanceof Number) {
-            return ((Number) value).intValue();
-        }
-        throw new IllegalArgumentException("Argument to size constraint must be a number");
-    }
-
-    /**
-     * Handles an individual criterion
-     *
-     * @param <T>
-     */
-    protected interface QueryHandler<T> {
-        void handle(EmbeddedQueryEncoder queryEncoder, T criterion, Document query, PersistentEntity entity);
-    }
-
-    /**
-     * Handles query operators when reading BSON
-     */
-    protected interface OperatorHandler {
-        void handle(Junction criteria, String attributeName, BsonReader queryReader);
-    }
-
-    /**
-     *
-     * Handles a projection
-     *
-     * @param <T>
-     */
-    protected interface ProjectionHandler<T extends Projection> {
-        /**
-         * Handles a projection modifying the aggregation pipeline appropriately
-         *
-         * @param entity        The entity
-         * @param groupByObject The group by object
-         * @param projection    The projection
-         * @return The key to be used to obtain the projected value from the pipeline results
-         */
-        String handle(PersistentEntity entity, Document projectObject, Document groupByObject, T projection);
-    }
-
-    protected static void populateBsonQuery(final EmbeddedQueryEncoder queryEncoder, Document query, List<Criterion> criteria, final PersistentEntity entity) {
-        // if a query combines more than 1 item, wrap the items in individual $and or $or arguments
-        // so that property names can't clash (e.g. for an $and containing two $ors)
-        for (Criterion criterion : criteria) {
-            final QueryHandler queryHandler = queryHandlers.get(criterion.getClass());
-            if (queryHandler != null) {
-                Document dbo = query;
-                if (criterion instanceof PropertyCriterion) {
-                    PropertyCriterion pc = (PropertyCriterion) criterion;
-                    PersistentProperty property = entity.getPropertyByName(pc.getProperty());
-                    if (property instanceof Custom) {
-                        CustomTypeMarshaller customTypeMarshaller = ((Custom) property).getCustomTypeMarshaller();
-                        if (!(customTypeMarshaller instanceof CodecCustomTypeMarshaller)) {
-                            customTypeMarshaller.query(property, pc, query);
-                            continue;
-                        }
-                    }
-                }
-                queryHandler.handle(queryEncoder, criterion, dbo, entity);
-            } else {
-                throw new InvalidDataAccessResourceUsageException("Queries of type " + criterion.getClass().getSimpleName() + " are not supported by this implementation");
-            }
-        }
-    }
-
-    protected static void populateBsonQuery(final EmbeddedQueryEncoder queryEncoder, Document query, Junction criteria, final PersistentEntity entity) {
-        List subList = null;
-        if (criteria instanceof Disjunction) {
-            subList = new ArrayList();
-            query.put(OR_OPERATOR, subList);
-        } else if (criteria instanceof Conjunction) {
-            subList = new ArrayList();
-            query.put(AND_OPERATOR, subList);
-        } else if (criteria instanceof Negation) {
-            subList = new ArrayList();
-            query.put(NOT_OPERATOR, new Document(OR_OPERATOR, subList));
-        }
-
-        for (Criterion criterion : criteria.getCriteria()) {
-            final QueryHandler queryHandler = queryHandlers.get(criterion.getClass());
-            if (queryHandler != null) {
-                Document dbo = query;
-                if (subList != null) {
-                    dbo = new Document();
-                    subList.add(dbo);
-                }
-
-                if (criterion instanceof PropertyCriterion) {
-                    PropertyCriterion pc = (PropertyCriterion) criterion;
-                    PersistentProperty property = entity.getPropertyByName(pc.getProperty());
-                    if (property instanceof Custom) {
-                        CustomTypeMarshaller customTypeMarshaller = ((Custom) property).getCustomTypeMarshaller();
-                        customTypeMarshaller.query(property, pc, query);
-                        continue;
-                    }
-                }
-                queryHandler.handle(queryEncoder, criterion, dbo, entity);
-            } else {
-                throw new InvalidDataAccessResourceUsageException("Queries of type " + criterion.getClass().getSimpleName() + " are not supported by this implementation");
-            }
-        }
-    }
-}


(grails-core) 07/14: Convert grails-data-hibernate7-spring-orm batch 1 to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 8cb1d488efec86a6268828266e627948e7b360fb
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 21:17:32 2026 -0500

    Convert grails-data-hibernate7-spring-orm batch 1 to Groovy
    
    Converts 14 of the 22 vendored Spring ORM support classes in
    org.grails.orm.hibernate.support.hibernate7 to @CompileStatic Groovy,
    preserving the upstream Spring copyright headers verbatim:
    HibernateCallback, HibernateJdbcException,
    HibernateObjectRetrievalFailureException,
    HibernateOptimisticLockingFailureException, HibernateQueryException,
    HibernateSystemException, SessionHolder, SpringJtaSessionContext,
    SpringFlushSynchronization, HibernateExceptionTranslator,
    ConfigurableJtaPlatform, SpringSessionContext,
    SpringSessionSynchronization and support.AsyncRequestInterceptor.
    
    Verified with codenarcMain/checkstyleMain on spring-orm and the full
    grails-data-hibernate7-core, grails-data-hibernate7 and
    grails-data-hibernate7-spring-boot test suites.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 ...latform.java => ConfigurableJtaPlatform.groovy} |  94 ++++++++--------
 ...rnateCallback.java => HibernateCallback.groovy} |  13 +--
 ...or.java => HibernateExceptionTranslator.groovy} |  55 +++++----
 ...xception.java => HibernateJdbcException.groovy} |  34 +++---
 ...ibernateObjectRetrievalFailureException.groovy} |  39 ++++---
 ...ernateOptimisticLockingFailureException.groovy} |  30 ++---
 ...ception.java => HibernateQueryException.groovy} |  26 ++---
 ...eption.java => HibernateSystemException.groovy} |  19 ++--
 .../{SessionHolder.java => SessionHolder.groovy}   |  59 +++++-----
 ...tion.java => SpringFlushSynchronization.groovy} |  33 +++---
 ...Context.java => SpringJtaSessionContext.groovy} |  30 ++---
 ...ionContext.java => SpringSessionContext.groovy} | 124 ++++++++++-----------
 ...on.java => SpringSessionSynchronization.groovy} | 109 +++++++++---------
 .../support/AsyncRequestInterceptor.groovy         | 123 ++++++++++++++++++++
 .../support/AsyncRequestInterceptor.java           | 123 --------------------
 15 files changed, 439 insertions(+), 472 deletions(-)

diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/ConfigurableJtaPlatform.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/ConfigurableJtaPlatform.groovy
similarity index 50%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/ConfigurableJtaPlatform.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/ConfigurableJtaPlatform.groovy
index a01d1d29a9..0abea61c8e 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/ConfigurableJtaPlatform.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/ConfigurableJtaPlatform.groovy
@@ -13,23 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.grails.orm.hibernate.support.hibernate7;
-
-import jakarta.transaction.Status;
-import jakarta.transaction.Synchronization;
-import jakarta.transaction.SystemException;
-import jakarta.transaction.Transaction;
-import jakarta.transaction.TransactionManager;
-import jakarta.transaction.TransactionSynchronizationRegistry;
-import jakarta.transaction.UserTransaction;
-
-import org.hibernate.TransactionException;
-import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.transaction.jta.UserTransactionAdapter;
-import org.springframework.util.Assert;
+package org.grails.orm.hibernate.support.hibernate7
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import jakarta.transaction.Status
+import jakarta.transaction.Synchronization
+import jakarta.transaction.SystemException
+import jakarta.transaction.Transaction
+import jakarta.transaction.TransactionManager
+import jakarta.transaction.TransactionSynchronizationRegistry
+import jakarta.transaction.UserTransaction
+import org.hibernate.TransactionException
+import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform
+import org.springframework.transaction.jta.UserTransactionAdapter
+import org.springframework.util.Assert
 
 /**
  * Implementation of Hibernate 5's JtaPlatform SPI, exposing passed-in {@link TransactionManager},
@@ -38,15 +36,16 @@ import org.springframework.util.Assert;
  * @author Juergen Hoeller
  * @since 4.2
  */
-@SuppressWarnings("serial")
+@CompileStatic
+@PackageScope
+@SuppressWarnings('serial')
 class ConfigurableJtaPlatform implements JtaPlatform {
 
-    private final TransactionManager transactionManager;
+    private final TransactionManager transactionManager
 
-    private final UserTransaction userTransaction;
+    private final UserTransaction userTransaction
 
-    @Nullable
-    private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
+    private final TransactionSynchronizationRegistry transactionSynchronizationRegistry
 
     /**
      * Create a new ConfigurableJtaPlatform instance with the given
@@ -55,57 +54,54 @@ class ConfigurableJtaPlatform implements JtaPlatform {
      * @param ut the JTA UserTransaction reference (optional)
      * @param tsr the JTA 1.1 TransactionSynchronizationRegistry (optional)
      */
-    public ConfigurableJtaPlatform(TransactionManager tm, @Nullable UserTransaction ut,
-            @Nullable TransactionSynchronizationRegistry tsr) {
-        Assert.notNull(tm, "TransactionManager reference must not be null");
-        this.transactionManager = tm;
-        this.userTransaction = (ut != null ? ut : new UserTransactionAdapter(tm));
-        this.transactionSynchronizationRegistry = tsr;
+    ConfigurableJtaPlatform(TransactionManager tm, UserTransaction ut,
+                            TransactionSynchronizationRegistry tsr) {
+        Assert.notNull(tm, 'TransactionManager reference must not be null')
+        this.transactionManager = tm
+        this.userTransaction = (ut != null ? ut : new UserTransactionAdapter(tm))
+        this.transactionSynchronizationRegistry = tsr
     }
 
     @Override
-    public TransactionManager retrieveTransactionManager() {
-        return this.transactionManager;
+    TransactionManager retrieveTransactionManager() {
+        return this.transactionManager
     }
 
     @Override
-    public UserTransaction retrieveUserTransaction() {
-        return this.userTransaction;
+    UserTransaction retrieveUserTransaction() {
+        return this.userTransaction
     }
 
     @Override
-    public Object getTransactionIdentifier(Transaction transaction) {
-        return transaction;
+    Object getTransactionIdentifier(Transaction transaction) {
+        return transaction
     }
 
     @Override
-    public boolean canRegisterSynchronization() {
+    boolean canRegisterSynchronization() {
         try {
-            return (this.transactionManager.getStatus() == Status.STATUS_ACTIVE);
-        }
-        catch (SystemException ex) {
-            throw new TransactionException("Could not determine JTA transaction status", ex);
+            return (this.transactionManager.getStatus() == Status.STATUS_ACTIVE)
+        } catch (SystemException ex) {
+            throw new TransactionException('Could not determine JTA transaction status', ex)
         }
     }
 
     @Override
-    public void registerSynchronization(Synchronization synchronization) {
+    void registerSynchronization(Synchronization synchronization) {
         if (this.transactionSynchronizationRegistry != null) {
-            this.transactionSynchronizationRegistry.registerInterposedSynchronization(synchronization);
-        }
-        else {
+            this.transactionSynchronizationRegistry.registerInterposedSynchronization(synchronization)
+        } else {
             try {
-                this.transactionManager.getTransaction().registerSynchronization(synchronization);
-            }
-            catch (Exception ex) {
-                throw new TransactionException("Could not access JTA Transaction to register synchronization", ex);
+                this.transactionManager.getTransaction().registerSynchronization(synchronization)
+            } catch (Exception ex) {
+                throw new TransactionException('Could not access JTA Transaction to register synchronization', ex)
             }
         }
     }
 
     @Override
-    public int getCurrentStatus() throws SystemException {
-        return this.transactionManager.getStatus();
+    int getCurrentStatus() throws SystemException {
+        return this.transactionManager.getStatus()
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateCallback.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateCallback.groovy
similarity index 86%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateCallback.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateCallback.groovy
index 3ddb30d0c0..11ba6f98a9 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateCallback.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateCallback.groovy
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.HibernateException;
-import org.hibernate.Session;
-import org.jspecify.annotations.Nullable;
+import org.hibernate.HibernateException
+import org.hibernate.Session
 
 /**
  * Callback interface for Hibernate code. To be used with {@link HibernateTemplate}'s
@@ -33,7 +31,7 @@ import org.jspecify.annotations.Nullable;
  * @see HibernateTransactionManager
  */
 @FunctionalInterface
-public interface HibernateCallback<T> {
+interface HibernateCallback<T> {
 
     /**
      * Gets called by {@code HibernateTemplate.execute} with an active
@@ -48,7 +46,6 @@ public interface HibernateCallback<T> {
      * @throws HibernateException if thrown by the Hibernate API
      * @see HibernateTemplate#execute
      */
-    @Nullable
-    T doInHibernate(Session session) throws HibernateException;
+    T doInHibernate(Session session) throws HibernateException
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateExceptionTranslator.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateExceptionTranslator.groovy
similarity index 70%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateExceptionTranslator.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateExceptionTranslator.groovy
index e67c3e3b56..228d7d48f6 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateExceptionTranslator.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateExceptionTranslator.groovy
@@ -13,19 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import jakarta.persistence.PersistenceException;
-
-import org.hibernate.HibernateException;
-import org.hibernate.JDBCException;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.dao.DataAccessException;
-import org.springframework.dao.support.PersistenceExceptionTranslator;
-import org.springframework.jdbc.support.SQLExceptionTranslator;
-import org.springframework.orm.jpa.EntityManagerFactoryUtils;
+import groovy.transform.CompileStatic
+import jakarta.persistence.PersistenceException
+import org.hibernate.HibernateException
+import org.hibernate.JDBCException
+import org.springframework.dao.DataAccessException
+import org.springframework.dao.support.PersistenceExceptionTranslator
+import org.springframework.jdbc.support.SQLExceptionTranslator
+import org.springframework.orm.jpa.EntityManagerFactoryUtils
 
 /**
  * {@link PersistenceExceptionTranslator} capable of translating {@link HibernateException}
@@ -44,10 +41,10 @@ import org.springframework.orm.jpa.EntityManagerFactoryUtils;
  * @see SessionFactoryUtils#convertHibernateAccessException(HibernateException)
  * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible(RuntimeException)
  */
-public class HibernateExceptionTranslator implements PersistenceExceptionTranslator {
+@CompileStatic
+class HibernateExceptionTranslator implements PersistenceExceptionTranslator {
 
-    @Nullable
-    private SQLExceptionTranslator jdbcExceptionTranslator;
+    private SQLExceptionTranslator jdbcExceptionTranslator
 
     /**
      * Set the JDBC exception translator for Hibernate exception translation purposes.
@@ -60,23 +57,22 @@ public class HibernateExceptionTranslator implements PersistenceExceptionTransla
      * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
      * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
      */
-    public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
-        this.jdbcExceptionTranslator = jdbcExceptionTranslator;
+    void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
+        this.jdbcExceptionTranslator = jdbcExceptionTranslator
     }
 
     @Override
-    @Nullable
-    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
-        if (ex instanceof HibernateException hibernateEx) {
-            return convertHibernateAccessException(hibernateEx);
+    DataAccessException translateExceptionIfPossible(RuntimeException ex) {
+        if (ex instanceof HibernateException) {
+            return convertHibernateAccessException((HibernateException) ex)
         }
         if (ex instanceof PersistenceException) {
-            if (ex.getCause() instanceof HibernateException hibernateEx) {
-                return convertHibernateAccessException(hibernateEx);
+            if (ex.getCause() instanceof HibernateException) {
+                return convertHibernateAccessException((HibernateException) ex.getCause())
             }
-            return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
+            return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex)
         }
-        return null;
+        return null
     }
 
     /**
@@ -89,14 +85,15 @@ public class HibernateExceptionTranslator implements PersistenceExceptionTransla
      * @see SessionFactoryUtils#convertHibernateAccessException
      */
     protected DataAccessException convertHibernateAccessException(HibernateException ex) {
-        if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException jdbcEx) {
+        if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) {
+            JDBCException jdbcEx = (JDBCException) ex
             DataAccessException dae = this.jdbcExceptionTranslator.translate(
-                    "Hibernate operation: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
+                    'Hibernate operation: ' + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException())
             if (dae != null) {
-                return dae;
+                return dae
             }
         }
-        return SessionFactoryUtils.convertHibernateAccessException(ex);
+        return SessionFactoryUtils.convertHibernateAccessException(ex)
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateJdbcException.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateJdbcException.groovy
similarity index 53%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateJdbcException.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateJdbcException.groovy
index 5e6e542e55..cf1760b536 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateJdbcException.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateJdbcException.groovy
@@ -13,15 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
+import java.sql.SQLException
 
-import java.sql.SQLException;
-
-import org.hibernate.JDBCException;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.dao.UncategorizedDataAccessException;
+import groovy.transform.CompileStatic
+import org.hibernate.JDBCException
+import org.springframework.dao.UncategorizedDataAccessException
 
 /**
  * Hibernate-specific subclass of UncategorizedDataAccessException,
@@ -31,29 +29,27 @@ import org.springframework.dao.UncategorizedDataAccessException;
  * @since 4.2
  * @see SessionFactoryUtils#convertHibernateAccessException
  */
-@SuppressWarnings("serial")
-public class HibernateJdbcException extends UncategorizedDataAccessException {
+@CompileStatic
+@SuppressWarnings('serial')
+class HibernateJdbcException extends UncategorizedDataAccessException {
 
-    public HibernateJdbcException(JDBCException ex) {
-        super("JDBC exception on Hibernate data access: SQLException for SQL [" + ex.getSQL() + "]; SQL state [" +
-                ex.getSQLState() + "]; error code [" + ex.getErrorCode() + "]; " + ex.getMessage(), ex);
+    HibernateJdbcException(JDBCException ex) {
+        super('JDBC exception on Hibernate data access: SQLException for SQL [' + ex.getSQL() + ']; SQL state [' +
+                ex.getSQLState() + ']; error code [' + ex.getErrorCode() + ']; ' + ex.getMessage(), ex)
     }
 
     /**
      * Return the underlying SQLException.
      */
-    @SuppressWarnings("NullAway")
-    public SQLException getSQLException() {
-        return ((JDBCException) getCause()).getSQLException();
+    SQLException getSQLException() {
+        return ((JDBCException) getCause()).getSQLException()
     }
 
     /**
      * Return the SQL that led to the problem.
      */
-    @Nullable
-    @SuppressWarnings("NullAway")
-    public String getSql() {
-        return ((JDBCException) getCause()).getSQL();
+    String getSql() {
+        return ((JDBCException) getCause()).getSQL()
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateObjectRetrievalFailureException.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateObjectRetrievalFailureException.groovy
similarity index 63%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateObjectRetrievalFailureException.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateObjectRetrievalFailureException.groovy
index 6559e7011a..616824aa63 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateObjectRetrievalFailureException.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateObjectRetrievalFailureException.groovy
@@ -13,16 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.HibernateException;
-import org.hibernate.UnresolvableObjectException;
-import org.hibernate.WrongClassException;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.orm.ObjectRetrievalFailureException;
-import org.springframework.util.ReflectionUtils;
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.hibernate.HibernateException
+import org.hibernate.UnresolvableObjectException
+import org.hibernate.WrongClassException
+import org.springframework.orm.ObjectRetrievalFailureException
+import org.springframework.util.ReflectionUtils
 
 /**
  * Hibernate-specific subclass of ObjectRetrievalFailureException.
@@ -32,26 +31,26 @@ import org.springframework.util.ReflectionUtils;
  * @since 4.2
  * @see SessionFactoryUtils#convertHibernateAccessException
  */
-@SuppressWarnings("serial")
-public class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException {
+@CompileStatic
+@SuppressWarnings('serial')
+class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException {
 
-    public HibernateObjectRetrievalFailureException(UnresolvableObjectException ex) {
-        super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex);
+    HibernateObjectRetrievalFailureException(UnresolvableObjectException ex) {
+        super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex)
     }
 
-    public HibernateObjectRetrievalFailureException(WrongClassException ex) {
-        super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex);
+    HibernateObjectRetrievalFailureException(WrongClassException ex) {
+        super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex)
     }
 
-    @Nullable
+    @PackageScope
     static Object getIdentifier(HibernateException hibEx) {
         try {
             // getIdentifier declares Serializable return value on 5.x but Object on 6.x
             // -> not binary compatible, let's invoke it reflectively for the time being
-            return ReflectionUtils.invokeMethod(hibEx.getClass().getMethod("getIdentifier"), hibEx);
-        }
-        catch (NoSuchMethodException ex) {
-            return null;
+            return ReflectionUtils.invokeMethod(hibEx.getClass().getMethod('getIdentifier'), hibEx)
+        } catch (NoSuchMethodException ex) {
+            return null
         }
     }
 
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOptimisticLockingFailureException.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOptimisticLockingFailureException.groovy
similarity index 59%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOptimisticLockingFailureException.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOptimisticLockingFailureException.groovy
index 523a2d161b..b46d148a7a 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOptimisticLockingFailureException.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOptimisticLockingFailureException.groovy
@@ -13,14 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.StaleObjectStateException;
-import org.hibernate.StaleStateException;
-import org.hibernate.dialect.lock.OptimisticEntityLockException;
-
-import org.springframework.orm.ObjectOptimisticLockingFailureException;
+import groovy.transform.CompileStatic
+import org.hibernate.StaleObjectStateException
+import org.hibernate.StaleStateException
+import org.hibernate.dialect.lock.OptimisticEntityLockException
+import org.springframework.orm.ObjectOptimisticLockingFailureException
 
 /**
  * Hibernate-specific subclass of ObjectOptimisticLockingFailureException.
@@ -31,19 +30,20 @@ import org.springframework.orm.ObjectOptimisticLockingFailureException;
  * @since 4.2
  * @see SessionFactoryUtils#convertHibernateAccessException
  */
-@SuppressWarnings("serial")
-public class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
+@CompileStatic
+@SuppressWarnings('serial')
+class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
 
-    public HibernateOptimisticLockingFailureException(StaleObjectStateException ex) {
-        super(ex.getEntityName(), HibernateObjectRetrievalFailureException.getIdentifier(ex), ex.getMessage(), ex);
+    HibernateOptimisticLockingFailureException(StaleObjectStateException ex) {
+        super(ex.getEntityName(), HibernateObjectRetrievalFailureException.getIdentifier(ex), ex.getMessage(), ex)
     }
 
-    public HibernateOptimisticLockingFailureException(StaleStateException ex) {
-        super(ex.getMessage(), ex);
+    HibernateOptimisticLockingFailureException(StaleStateException ex) {
+        super(ex.getMessage(), ex)
     }
 
-    public HibernateOptimisticLockingFailureException(OptimisticEntityLockException ex) {
-        super(ex.getMessage(), ex);
+    HibernateOptimisticLockingFailureException(OptimisticEntityLockException ex) {
+        super(ex.getMessage(), ex)
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateQueryException.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateQueryException.groovy
similarity index 65%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateQueryException.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateQueryException.groovy
index 03a2bf2712..0c4977d63a 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateQueryException.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateQueryException.groovy
@@ -13,13 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.QueryException;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.dao.InvalidDataAccessResourceUsageException;
+import groovy.transform.CompileStatic
+import org.hibernate.QueryException
+import org.springframework.dao.InvalidDataAccessResourceUsageException
 
 /**
  * Hibernate-specific subclass of InvalidDataAccessResourceUsageException,
@@ -29,20 +27,20 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
  * @since 4.2
  * @see SessionFactoryUtils#convertHibernateAccessException
  */
-@SuppressWarnings("serial")
-public class HibernateQueryException extends InvalidDataAccessResourceUsageException {
+@CompileStatic
+@SuppressWarnings('serial')
+class HibernateQueryException extends InvalidDataAccessResourceUsageException {
 
-    public HibernateQueryException(QueryException ex) {
-        super(ex.getMessage(), ex);
+    HibernateQueryException(QueryException ex) {
+        super(ex.getMessage(), ex)
     }
 
     /**
      * Return the HQL query string that was invalid.
      */
-    @Nullable
-    public String getQueryString() {
-        QueryException cause = (QueryException) getCause();
-        return (cause != null ? cause.getQueryString() : null);
+    String getQueryString() {
+        QueryException cause = (QueryException) getCause()
+        return (cause != null ? cause.getQueryString() : null)
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateSystemException.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateSystemException.groovy
similarity index 70%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateSystemException.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateSystemException.groovy
index dbfadb0d78..65790b227d 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateSystemException.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateSystemException.groovy
@@ -13,13 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.HibernateException;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.dao.UncategorizedDataAccessException;
+import groovy.transform.CompileStatic
+import org.hibernate.HibernateException
+import org.springframework.dao.UncategorizedDataAccessException
 
 /**
  * Hibernate-specific subclass of UncategorizedDataAccessException,
@@ -30,16 +28,17 @@ import org.springframework.dao.UncategorizedDataAccessException;
  * @since 4.2
  * @see SessionFactoryUtils#convertHibernateAccessException
  */
-@SuppressWarnings("serial")
-public class HibernateSystemException extends UncategorizedDataAccessException {
+@CompileStatic
+@SuppressWarnings('serial')
+class HibernateSystemException extends UncategorizedDataAccessException {
 
     /**
      * Create a new HibernateSystemException,
      * wrapping an arbitrary HibernateException.
      * @param cause the HibernateException thrown
      */
-    public HibernateSystemException(@Nullable HibernateException cause) {
-        super(cause != null ? cause.getMessage() : null, cause);
+    HibernateSystemException(HibernateException cause) {
+        super(cause != null ? cause.getMessage() : null, cause)
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionHolder.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionHolder.groovy
similarity index 51%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionHolder.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionHolder.groovy
index 7a03919370..08746fff8a 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionHolder.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SessionHolder.groovy
@@ -13,15 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.FlushMode;
-import org.hibernate.Session;
-import org.hibernate.Transaction;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.orm.jpa.EntityManagerHolder;
+import groovy.transform.CompileStatic
+import org.hibernate.FlushMode
+import org.hibernate.Session
+import org.hibernate.Transaction
+import org.springframework.orm.jpa.EntityManagerHolder
 
 /**
  * Resource holder wrapping a Hibernate {@link Session} (plus an optional {@link Transaction}).
@@ -36,46 +34,43 @@ import org.springframework.orm.jpa.EntityManagerHolder;
  * @see HibernateTransactionManager
  * @see SessionFactoryUtils
  */
-public class SessionHolder extends EntityManagerHolder {
+@CompileStatic
+class SessionHolder extends EntityManagerHolder {
 
-    @Nullable
-    private Transaction transaction;
+    private Transaction transaction
 
-    @Nullable
-    private FlushMode previousFlushMode;
+    private FlushMode previousFlushMode
 
-    public SessionHolder(Session session) {
-        super(session);
+    SessionHolder(Session session) {
+        super(session)
     }
 
-    public Session getSession() {
-        return (Session) getEntityManager();
+    Session getSession() {
+        return (Session) getEntityManager()
     }
 
-    public void setTransaction(@Nullable Transaction transaction) {
-        this.transaction = transaction;
-        setTransactionActive(transaction != null);
+    void setTransaction(Transaction transaction) {
+        this.transaction = transaction
+        setTransactionActive(transaction != null)
     }
 
-    @Nullable
-    public Transaction getTransaction() {
-        return this.transaction;
+    Transaction getTransaction() {
+        return this.transaction
     }
 
-    public void setPreviousFlushMode(@Nullable FlushMode previousFlushMode) {
-        this.previousFlushMode = previousFlushMode;
+    void setPreviousFlushMode(FlushMode previousFlushMode) {
+        this.previousFlushMode = previousFlushMode
     }
 
-    @Nullable
-    public FlushMode getPreviousFlushMode() {
-        return this.previousFlushMode;
+    FlushMode getPreviousFlushMode() {
+        return this.previousFlushMode
     }
 
     @Override
-    public void clear() {
-        super.clear();
-        this.transaction = null;
-        this.previousFlushMode = null;
+    void clear() {
+        super.clear()
+        this.transaction = null
+        this.previousFlushMode = null
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringFlushSynchronization.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringFlushSynchronization.groovy
similarity index 56%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringFlushSynchronization.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringFlushSynchronization.groovy
index 253858e2a0..2c793d0cec 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringFlushSynchronization.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringFlushSynchronization.groovy
@@ -13,13 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.Session;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.transaction.support.TransactionSynchronization;
+import groovy.transform.CompileStatic
+import org.hibernate.Session
+import org.springframework.transaction.support.TransactionSynchronization
 
 /**
  * Simple synchronization adapter that propagates a {@code flush()} call
@@ -28,27 +26,30 @@ import org.springframework.transaction.support.TransactionSynchronization;
  * @author Juergen Hoeller
  * @since 4.2
  */
-public class SpringFlushSynchronization implements TransactionSynchronization {
+@CompileStatic
+class SpringFlushSynchronization implements TransactionSynchronization {
 
-    private final Session session;
+    private final Session session
 
-    public SpringFlushSynchronization(Session session) {
-        this.session = session;
+    SpringFlushSynchronization(Session session) {
+        this.session = session
     }
 
     @Override
-    public void flush() {
-        SessionFactoryUtils.flush(this.session, false);
+    void flush() {
+        SessionFactoryUtils.flush(this.session, false)
     }
 
     @Override
-    public boolean equals(@Nullable Object other) {
-        return (this == other || (other instanceof SpringFlushSynchronization that && this.session == that.session));
+    boolean equals(Object other) {
+        // Reference identity on both counts, as in the original.
+        return this.is(other) ||
+                (other instanceof SpringFlushSynchronization && this.session.is(((SpringFlushSynchronization) other).@session))
     }
 
     @Override
-    public int hashCode() {
-        return this.session.hashCode();
+    int hashCode() {
+        return this.session.hashCode()
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringJtaSessionContext.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringJtaSessionContext.groovy
similarity index 63%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringJtaSessionContext.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringJtaSessionContext.groovy
index 914384f4e3..31fbed7a1e 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringJtaSessionContext.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringJtaSessionContext.groovy
@@ -13,15 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.FlushMode;
-import org.hibernate.Session;
-import org.hibernate.context.internal.JTASessionContext;
-import org.hibernate.engine.spi.SessionFactoryImplementor;
-
-import org.springframework.transaction.support.TransactionSynchronizationManager;
+import groovy.transform.CompileStatic
+import org.hibernate.FlushMode
+import org.hibernate.Session
+import org.hibernate.context.internal.JTASessionContext
+import org.hibernate.engine.spi.SessionFactoryImplementor
+import org.springframework.transaction.support.TransactionSynchronizationManager
 
 /**
  * Spring-specific subclass of Hibernate's JTASessionContext,
@@ -30,20 +29,21 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
  * @author Juergen Hoeller
  * @since 4.2
  */
-@SuppressWarnings("serial")
-public class SpringJtaSessionContext extends JTASessionContext {
+@CompileStatic
+@SuppressWarnings('serial')
+class SpringJtaSessionContext extends JTASessionContext {
 
-    public SpringJtaSessionContext(SessionFactoryImplementor factory) {
-        super(factory);
+    SpringJtaSessionContext(SessionFactoryImplementor factory) {
+        super(factory)
     }
 
     @Override
     protected Session buildOrObtainSession() {
-        Session session = super.buildOrObtainSession();
+        Session session = super.buildOrObtainSession()
         if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
-            session.setHibernateFlushMode(FlushMode.MANUAL);
+            session.setHibernateFlushMode(FlushMode.MANUAL)
         }
-        return session;
+        return session
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionContext.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionContext.groovy
similarity index 60%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionContext.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionContext.groovy
index a1c5a1cb78..b884f86079 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionContext.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionContext.groovy
@@ -13,24 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import jakarta.transaction.Status;
-import jakarta.transaction.SystemException;
-import jakarta.transaction.TransactionManager;
-
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.FlushMode;
-import org.hibernate.HibernateException;
-import org.hibernate.Session;
-import org.hibernate.context.spi.CurrentSessionContext;
-import org.hibernate.engine.spi.SessionFactoryImplementor;
-import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.orm.jpa.EntityManagerHolder;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
+import groovy.transform.CompileStatic
+import jakarta.transaction.Status
+import jakarta.transaction.SystemException
+import jakarta.transaction.TransactionManager
+import org.apache.commons.logging.LogFactory
+import org.hibernate.FlushMode
+import org.hibernate.HibernateException
+import org.hibernate.Session
+import org.hibernate.context.spi.CurrentSessionContext
+import org.hibernate.engine.spi.SessionFactoryImplementor
+import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform
+import org.springframework.orm.jpa.EntityManagerHolder
+import org.springframework.transaction.support.TransactionSynchronizationManager
 
 /**
  * Implementation of Hibernate 3.1's {@link CurrentSessionContext} interface
@@ -44,101 +41,94 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
  * @author Juergen Hoeller
  * @since 4.2
  */
-@SuppressWarnings("serial")
-public class SpringSessionContext implements CurrentSessionContext {
+@CompileStatic
+@SuppressWarnings('serial')
+class SpringSessionContext implements CurrentSessionContext {
 
-    private final SessionFactoryImplementor sessionFactory;
+    private final SessionFactoryImplementor sessionFactory
 
-    @Nullable
-    private TransactionManager transactionManager;
+    private TransactionManager transactionManager
 
-    @Nullable
-    private CurrentSessionContext jtaSessionContext;
+    private CurrentSessionContext jtaSessionContext
 
     /**
      * Create a new SpringSessionContext for the given Hibernate SessionFactory.
      * @param sessionFactory the SessionFactory to provide current Sessions for
      */
-    public SpringSessionContext(SessionFactoryImplementor sessionFactory) {
-        this.sessionFactory = sessionFactory;
+    SpringSessionContext(SessionFactoryImplementor sessionFactory) {
+        this.sessionFactory = sessionFactory
         try {
-            JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry().getService(JtaPlatform.class);
-            this.transactionManager = jtaPlatform.retrieveTransactionManager();
+            JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry().getService(JtaPlatform)
+            this.transactionManager = jtaPlatform.retrieveTransactionManager()
             if (this.transactionManager != null) {
-                this.jtaSessionContext = new SpringJtaSessionContext(sessionFactory);
+                this.jtaSessionContext = new SpringJtaSessionContext(sessionFactory)
             }
-        }
-        catch (Exception ex) {
-            LogFactory.getLog(SpringSessionContext.class).warn(
-                    "Could not introspect Hibernate JtaPlatform for SpringJtaSessionContext", ex);
+        } catch (Exception ex) {
+            LogFactory.getLog(SpringSessionContext).warn(
+                    'Could not introspect Hibernate JtaPlatform for SpringJtaSessionContext', ex)
         }
     }
 
-
     /**
      * Retrieve the Spring-managed Session for the current thread, if any.
      */
     @Override
-    public Session currentSession() throws HibernateException {
-        Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
-        if (value instanceof Session session) {
-            return session;
-        }
-        else if (value instanceof SessionHolder sessionHolder) {
+    Session currentSession() throws HibernateException {
+        Object value = TransactionSynchronizationManager.getResource(this.sessionFactory)
+        if (value instanceof Session) {
+            return (Session) value
+        } else if (value instanceof SessionHolder) {
             // HibernateTransactionManager
-            Session session = sessionHolder.getSession();
+            SessionHolder sessionHolder = (SessionHolder) value
+            Session session = sessionHolder.getSession()
             if (!sessionHolder.isSynchronizedWithTransaction() &&
                     TransactionSynchronizationManager.isSynchronizationActive()) {
                 TransactionSynchronizationManager.registerSynchronization(
-                        new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
-                sessionHolder.setSynchronizedWithTransaction(true);
+                        new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false))
+                sessionHolder.setSynchronizedWithTransaction(true)
                 // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
                 // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
-                FlushMode flushMode = session.getHibernateFlushMode();
-                if (flushMode.equals(FlushMode.MANUAL) &&
+                FlushMode flushMode = session.getHibernateFlushMode()
+                if (flushMode == FlushMode.MANUAL &&
                         !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
-                    session.setHibernateFlushMode(FlushMode.AUTO);
-                    sessionHolder.setPreviousFlushMode(flushMode);
+                    session.setHibernateFlushMode(FlushMode.AUTO)
+                    sessionHolder.setPreviousFlushMode(flushMode)
                 }
             }
-            return session;
-        }
-        else if (value instanceof EntityManagerHolder entityManagerHolder) {
+            return session
+        } else if (value instanceof EntityManagerHolder) {
             // JpaTransactionManager
-            return entityManagerHolder.getEntityManager().unwrap(Session.class);
+            return ((EntityManagerHolder) value).getEntityManager().unwrap(Session)
         }
 
         if (this.transactionManager != null && this.jtaSessionContext != null) {
             try {
                 if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
-                    Session session = this.jtaSessionContext.currentSession();
+                    Session session = this.jtaSessionContext.currentSession()
                     if (TransactionSynchronizationManager.isSynchronizationActive()) {
                         TransactionSynchronizationManager.registerSynchronization(
-                                new SpringFlushSynchronization(session));
+                                new SpringFlushSynchronization(session))
                     }
-                    return session;
+                    return session
                 }
-            }
-            catch (SystemException ex) {
-                throw new HibernateException("JTA TransactionManager found but status check failed", ex);
+            } catch (SystemException ex) {
+                throw new HibernateException('JTA TransactionManager found but status check failed', ex)
             }
         }
 
         if (TransactionSynchronizationManager.isSynchronizationActive()) {
-            Session session = this.sessionFactory.openSession();
+            Session session = this.sessionFactory.openSession()
             if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
-                session.setHibernateFlushMode(FlushMode.MANUAL);
+                session.setHibernateFlushMode(FlushMode.MANUAL)
             }
-            SessionHolder sessionHolder = new SessionHolder(session);
+            SessionHolder sessionHolder = new SessionHolder(session)
             TransactionSynchronizationManager.registerSynchronization(
-                    new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
-            TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
-            sessionHolder.setSynchronizedWithTransaction(true);
-            return session;
-        }
-        else {
-            throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
+                    new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true))
+            TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder)
+            sessionHolder.setSynchronizedWithTransaction(true)
+            return session
         }
+        throw new HibernateException('Could not obtain transaction-synchronized Session for current thread')
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionSynchronization.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionSynchronization.groovy
similarity index 55%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionSynchronization.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionSynchronization.groovy
index 3755feefc0..2824e89320 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionSynchronization.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/SpringSessionSynchronization.groovy
@@ -13,18 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.grails.orm.hibernate.support.hibernate7;
-
-import org.hibernate.FlushMode;
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.hibernate.engine.spi.SessionImplementor;
-
-import org.springframework.core.Ordered;
-import org.springframework.dao.DataAccessException;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
+package org.grails.orm.hibernate.support.hibernate7
+
+import groovy.transform.CompileStatic
+import org.hibernate.FlushMode
+import org.hibernate.Session
+import org.hibernate.SessionFactory
+import org.hibernate.engine.spi.SessionImplementor
+import org.springframework.core.Ordered
+import org.springframework.dao.DataAccessException
+import org.springframework.transaction.support.TransactionSynchronization
+import org.springframework.transaction.support.TransactionSynchronizationManager
 
 /**
  * Callback for resource cleanup at the end of a Spring-managed transaction
@@ -33,111 +32,111 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
  * @author Juergen Hoeller
  * @since 4.2
  */
-public class SpringSessionSynchronization implements TransactionSynchronization, Ordered {
+@CompileStatic
+class SpringSessionSynchronization implements TransactionSynchronization, Ordered {
 
-    private final SessionHolder sessionHolder;
+    private final SessionHolder sessionHolder
 
-    private final SessionFactory sessionFactory;
+    private final SessionFactory sessionFactory
 
-    private final boolean newSession;
+    private final boolean newSession
 
-    private boolean holderActive = true;
+    private boolean holderActive = true
 
-    public SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory) {
-        this(sessionHolder, sessionFactory, false);
+    SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory) {
+        this(sessionHolder, sessionFactory, false)
     }
 
-    public SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory, boolean newSession) {
-        this.sessionHolder = sessionHolder;
-        this.sessionFactory = sessionFactory;
-        this.newSession = newSession;
+    SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory, boolean newSession) {
+        this.sessionHolder = sessionHolder
+        this.sessionFactory = sessionFactory
+        this.newSession = newSession
     }
 
     private Session getCurrentSession() {
-        return this.sessionHolder.getSession();
+        return this.sessionHolder.getSession()
     }
 
     @Override
-    public int getOrder() {
-        return SessionFactoryUtils.SESSION_SYNCHRONIZATION_ORDER;
+    int getOrder() {
+        return SessionFactoryUtils.SESSION_SYNCHRONIZATION_ORDER
     }
 
     @Override
-    public void suspend() {
+    void suspend() {
         if (this.holderActive) {
-            TransactionSynchronizationManager.unbindResource(this.sessionFactory);
+            TransactionSynchronizationManager.unbindResource(this.sessionFactory)
             // Eagerly disconnect the Session here, to make release mode "on_close" work on JBoss.
-            Session session = getCurrentSession();
-            if (session instanceof SessionImplementor sessionImpl) {
-                sessionImpl.getJdbcCoordinator().getLogicalConnection().manualDisconnect();
+            Session session = getCurrentSession()
+            if (session instanceof SessionImplementor) {
+                ((SessionImplementor) session).getJdbcCoordinator().getLogicalConnection().manualDisconnect()
             }
         }
     }
 
     @Override
-    public void resume() {
+    void resume() {
         if (this.holderActive) {
-            TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
+            TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder)
         }
     }
 
     @Override
-    public void flush() {
-        SessionFactoryUtils.flush(getCurrentSession(), false);
+    void flush() {
+        SessionFactoryUtils.flush(getCurrentSession(), false)
     }
 
     @Override
-    public void beforeCommit(boolean readOnly) throws DataAccessException {
+    void beforeCommit(boolean readOnly) throws DataAccessException {
         if (!readOnly) {
-            Session session = getCurrentSession();
+            Session session = getCurrentSession()
             // Read-write transaction -> flush the Hibernate Session.
             // Further check: only flush when not FlushMode.MANUAL.
-            if (!FlushMode.MANUAL.equals(session.getHibernateFlushMode())) {
-                SessionFactoryUtils.flush(getCurrentSession(), true);
+            if (session.getHibernateFlushMode() != FlushMode.MANUAL) {
+                SessionFactoryUtils.flush(getCurrentSession(), true)
             }
         }
     }
 
     @Override
-    public void beforeCompletion() {
+    void beforeCompletion() {
         try {
-            Session session = this.sessionHolder.getSession();
+            Session session = this.sessionHolder.getSession()
             if (this.sessionHolder.getPreviousFlushMode() != null) {
                 // In case of pre-bound Session, restore previous flush mode.
-                session.setHibernateFlushMode(this.sessionHolder.getPreviousFlushMode());
+                session.setHibernateFlushMode(this.sessionHolder.getPreviousFlushMode())
             }
             // Eagerly disconnect the Session here, to make release mode "on_close" work nicely.
-            if (session instanceof SessionImplementor sessionImpl) {
-                sessionImpl.getJdbcCoordinator().getLogicalConnection().manualDisconnect();
+            if (session instanceof SessionImplementor) {
+                ((SessionImplementor) session).getJdbcCoordinator().getLogicalConnection().manualDisconnect()
             }
-        }
-        finally {
+        } finally {
             // Unbind at this point if it's a new Session...
             if (this.newSession) {
-                TransactionSynchronizationManager.unbindResource(this.sessionFactory);
-                this.holderActive = false;
+                TransactionSynchronizationManager.unbindResource(this.sessionFactory)
+                this.holderActive = false
             }
         }
     }
 
     @Override
-    public void afterCommit() {
+    void afterCommit() {
+        // no-op
     }
 
     @Override
-    public void afterCompletion(int status) {
+    void afterCompletion(int status) {
         try {
             if (status != STATUS_COMMITTED) {
                 // Clear all pending inserts/updates/deletes in the Session.
                 // Necessary for pre-bound Sessions, to avoid inconsistent state.
-                this.sessionHolder.getSession().clear();
+                this.sessionHolder.getSession().clear()
             }
-        }
-        finally {
-            this.sessionHolder.setSynchronizedWithTransaction(false);
+        } finally {
+            this.sessionHolder.setSynchronizedWithTransaction(false)
             // Call close() at this point if it's a new Session...
             if (this.newSession) {
-                SessionFactoryUtils.closeSession(this.sessionHolder.getSession());
+                SessionFactoryUtils.closeSession(this.sessionHolder.getSession())
             }
         }
     }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.groovy b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.groovy
new file mode 100644
index 0000000000..a82576fcd1
--- /dev/null
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.groovy
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.grails.orm.hibernate.support.hibernate7.support
+
+import java.util.concurrent.Callable
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.hibernate.SessionFactory
+import org.springframework.transaction.support.TransactionSynchronizationManager
+import org.springframework.web.context.request.NativeWebRequest
+import org.springframework.web.context.request.async.CallableProcessingInterceptor
+import org.springframework.web.context.request.async.DeferredResult
+import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor
+
+import org.grails.orm.hibernate.support.hibernate7.SessionFactoryUtils
+import org.grails.orm.hibernate.support.hibernate7.SessionHolder
+
+/**
+ * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and
+ * OpenSessionInViewInterceptor.
+ *
+ * Ensures the following:
+ * 1) The session is bound/unbound when "callable processing" is started
+ * 2) The session is closed if an async request times out or an error occurred
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.2
+ */
+@CompileStatic
+@PackageScope
+class AsyncRequestInterceptor implements CallableProcessingInterceptor, DeferredResultProcessingInterceptor {
+
+    private static final Log LOG = LogFactory.getLog(AsyncRequestInterceptor)
+
+    private final SessionFactory sessionFactory
+
+    private final SessionHolder sessionHolder
+
+    private volatile boolean timeoutInProgress
+
+    private volatile boolean errorInProgress
+
+    AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) {
+        this.sessionFactory = sessionFactory
+        this.sessionHolder = sessionHolder
+    }
+
+    @Override
+    def <T> void preProcess(NativeWebRequest request, Callable<T> task) {
+        bindSession()
+    }
+
+    void bindSession() {
+        this.timeoutInProgress = false
+        this.errorInProgress = false
+        TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder)
+    }
+
+    @Override
+    def <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) {
+        TransactionSynchronizationManager.unbindResource(this.sessionFactory)
+    }
+
+    @Override
+    def <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) {
+        this.timeoutInProgress = true
+        return RESULT_NONE  // give other interceptors a chance to handle the timeout
+    }
+
+    @Override
+    def <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) {
+        this.errorInProgress = true
+        return RESULT_NONE  // give other interceptors a chance to handle the error
+    }
+
+    @Override
+    def <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
+        closeSession()
+    }
+
+    private void closeSession() {
+        if (this.timeoutInProgress || this.errorInProgress) {
+            LOG.debug('Closing Hibernate Session after async request timeout/error')
+            SessionFactoryUtils.closeSession(this.sessionHolder.getSession())
+        }
+    }
+
+    // Implementation of DeferredResultProcessingInterceptor methods
+
+    @Override
+    def <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) {
+        this.timeoutInProgress = true
+        return true  // give other interceptors a chance to handle the timeout
+    }
+
+    @Override
+    def <T> boolean handleError(NativeWebRequest request, DeferredResult<T> deferredResult, Throwable t) {
+        this.errorInProgress = true
+        return true  // give other interceptors a chance to handle the error
+    }
+
+    @Override
+    def <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) {
+        closeSession()
+    }
+
+}
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.java
deleted file mode 100644
index 161687d957..0000000000
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/support/AsyncRequestInterceptor.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2002-present the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.grails.orm.hibernate.support.hibernate7.support;
-
-import java.util.concurrent.Callable;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.SessionFactory;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-import org.springframework.web.context.request.NativeWebRequest;
-import org.springframework.web.context.request.async.CallableProcessingInterceptor;
-import org.springframework.web.context.request.async.DeferredResult;
-import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
-
-import org.grails.orm.hibernate.support.hibernate7.SessionFactoryUtils;
-import org.grails.orm.hibernate.support.hibernate7.SessionHolder;
-
-/**
- * An interceptor with asynchronous web requests used in OpenSessionInViewFilter and
- * OpenSessionInViewInterceptor.
- *
- * Ensures the following:
- * 1) The session is bound/unbound when "callable processing" is started
- * 2) The session is closed if an async request times out or an error occurred
- *
- * @author Rossen Stoyanchev
- * @since 4.2
- */
-class AsyncRequestInterceptor implements CallableProcessingInterceptor, DeferredResultProcessingInterceptor {
-
-    private static final Log logger = LogFactory.getLog(AsyncRequestInterceptor.class);
-
-    private final SessionFactory sessionFactory;
-
-    private final SessionHolder sessionHolder;
-
-    private volatile boolean timeoutInProgress;
-
-    private volatile boolean errorInProgress;
-
-    public AsyncRequestInterceptor(SessionFactory sessionFactory, SessionHolder sessionHolder) {
-        this.sessionFactory = sessionFactory;
-        this.sessionHolder = sessionHolder;
-    }
-
-    @Override
-    public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
-        bindSession();
-    }
-
-    public void bindSession() {
-        this.timeoutInProgress = false;
-        this.errorInProgress = false;
-        TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
-    }
-
-    @Override
-    public <T> void postProcess(NativeWebRequest request, Callable<T> task, @Nullable Object concurrentResult) {
-        TransactionSynchronizationManager.unbindResource(this.sessionFactory);
-    }
-
-    @Override
-    public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) {
-        this.timeoutInProgress = true;
-        return RESULT_NONE;  // give other interceptors a chance to handle the timeout
-    }
-
-    @Override
-    public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) {
-        this.errorInProgress = true;
-        return RESULT_NONE;  // give other interceptors a chance to handle the error
-    }
-
-    @Override
-    public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
-        closeSession();
-    }
-
-    private void closeSession() {
-        if (this.timeoutInProgress || this.errorInProgress) {
-            logger.debug("Closing Hibernate Session after async request timeout/error");
-            SessionFactoryUtils.closeSession(this.sessionHolder.getSession());
-        }
-    }
-
-
-    // Implementation of DeferredResultProcessingInterceptor methods
-
-    @Override
-    public <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) {
-        this.timeoutInProgress = true;
-        return true;  // give other interceptors a chance to handle the timeout
-    }
-
-    @Override
-    public <T> boolean handleError(NativeWebRequest request, DeferredResult<T> deferredResult, Throwable t) {
-        this.errorInProgress = true;
-        return true;  // give other interceptors a chance to handle the error
-    }
-
-    @Override
-    public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) {
-        closeSession();
-    }
-
-}


(grails-core) 14/14: Convert grails-data-hibernate7-dbmigration-core snapshot package to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 98aa2ddb56ecc511f58d2676406fd674300e128d
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Wed Sep 9 08:11:05 2026 -0500

    Convert grails-data-hibernate7-dbmigration-core snapshot package to Groovy
    
    Convert the liquibase.ext.hibernate.snapshot and snapshot.extension
    classes (13 files) from Java to statically compiled Groovy, completing
    the Java-to-Groovy migration of this module. Existing headers are
    preserved verbatim; header-less files remain header-less.
    
    Notable adjustments:
    - varargs constructors/arrays use explicit Class[] casts so the STC
      keeps the SnapshotGenerator generic signatures
    - fluent covariant setters on liquibase structure types are split into
      separate statements
    - Java regex string literals become slashy strings
    
    Now that the module has Groovy sources, groovydoc actually processes it
    and failed with NoClassDefFoundError: org/jboss/logging/Logger, because
    hibernate-core's Gradle metadata exposes jboss-logging at runtime only.
    Declare it compileOnly (version managed by the Spring Boot BOM) so the
    Groovydoc classpath can resolve the documented Hibernate types.
    
    Verified with checkstyleMain/codenarcMain, groovydoc, assemble,
    validateDependencyVersions and the full
    grails-data-hibernate7-dbmigration-core (25 tests) and
    grails-data-hibernate7-dbmigration (86 tests) suites.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 .../dbmigration-core/build.gradle                  |   3 +
 .../HibernateCatalogSnapshotGenerator.groovy       |  41 +++
 .../HibernateCatalogSnapshotGenerator.java         |  35 ---
 .../HibernateColumnSnapshotGenerator.groovy        | 321 +++++++++++++++++++++
 .../snapshot/HibernateColumnSnapshotGenerator.java | 310 --------------------
 .../HibernateForeignKeySnapshotGenerator.groovy    |  85 ++++++
 .../HibernateForeignKeySnapshotGenerator.java      |  80 -----
 .../HibernateIndexSnapshotGenerator.groovy         | 115 ++++++++
 .../snapshot/HibernateIndexSnapshotGenerator.java  | 111 -------
 .../HibernatePrimaryKeySnapshotGenerator.groovy    |  82 ++++++
 .../HibernatePrimaryKeySnapshotGenerator.java      |  77 -----
 .../HibernateSchemaSnapshotGenerator.groovy        |  44 +++
 .../snapshot/HibernateSchemaSnapshotGenerator.java |  39 ---
 .../HibernateSequenceSnapshotGenerator.groovy      |  59 ++++
 .../HibernateSequenceSnapshotGenerator.java        |  57 ----
 .../snapshot/HibernateSnapshotGenerator.groovy     | 136 +++++++++
 .../snapshot/HibernateSnapshotGenerator.java       | 136 ---------
 .../HibernateTableSnapshotGenerator.groovy         |  96 ++++++
 .../snapshot/HibernateTableSnapshotGenerator.java  |  91 ------
 ...bernateUniqueConstraintSnapshotGenerator.groovy | 142 +++++++++
 ...HibernateUniqueConstraintSnapshotGenerator.java | 140 ---------
 .../snapshot/HibernateViewSnapshotGenerator.groovy |  40 +++
 .../snapshot/HibernateViewSnapshotGenerator.java   |  36 ---
 .../extension/ExtendedSnapshotGenerator.groovy     |  12 +
 .../extension/ExtendedSnapshotGenerator.java       |   8 -
 .../TableGeneratorSnapshotGenerator.groovy         |  54 ++++
 .../extension/TableGeneratorSnapshotGenerator.java |  50 ----
 27 files changed, 1230 insertions(+), 1170 deletions(-)

diff --git a/grails-data-hibernate7/dbmigration-core/build.gradle b/grails-data-hibernate7/dbmigration-core/build.gradle
index d3bd7cf828..4bbb828892 100644
--- a/grails-data-hibernate7/dbmigration-core/build.gradle
+++ b/grails-data-hibernate7/dbmigration-core/build.gradle
@@ -49,6 +49,9 @@ dependencies {
     implementation("org.liquibase:liquibase-core")
     implementation("org.hibernate.orm:hibernate-core")
     implementation("org.hibernate.models:hibernate-models")
+    // hibernate-core's Gradle metadata exposes jboss-logging at runtime only, but Groovydoc
+    // resolves the documented Hibernate types with Class.forName and needs it on the compile classpath.
+    compileOnly("org.jboss.logging:jboss-logging")
     compileOnly("org.hibernate.orm:hibernate-envers")
     testCompileOnly("org.hibernate.orm:hibernate-envers")
 
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.groovy
new file mode 100644
index 0000000000..d56d7ffc30
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.groovy
@@ -0,0 +1,41 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.exception.DatabaseException
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.CatalogSnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Catalog
+
+/**
+ * Hibernate doesn't really support Catalogs, so just return the passed example back as if it had all the info it needed.
+ */
+@CompileStatic
+class HibernateCatalogSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    HibernateCatalogSnapshotGenerator() {
+        super(Catalog)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        Catalog catalog = new Catalog(snapshot.getDatabase().getDefaultCatalogName())
+        catalog.setDefault(true)
+        return catalog
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        // Nothing to add to
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [CatalogSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java
deleted file mode 100644
index 6ad1e560eb..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import liquibase.exception.DatabaseException;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Catalog;
-
-/**
- * Hibernate doesn't really support Catalogs, so just return the passed example back as if it had all the info it needed.
- */
-public class HibernateCatalogSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    public HibernateCatalogSnapshotGenerator() {
-        super(Catalog.class);
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        return new Catalog(snapshot.getDatabase().getDefaultCatalogName()).setDefault(true);
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        // Nothing to add to
-    }
-
-    @Override
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.CatalogSnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.groovy
new file mode 100644
index 0000000000..ce5150802b
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.groovy
@@ -0,0 +1,321 @@
+package liquibase.ext.hibernate.snapshot
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.datatype.DataTypeFactory
+import liquibase.datatype.core.UnknownType
+import liquibase.exception.DatabaseException
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.ColumnSnapshotGenerator
+import liquibase.statement.DatabaseFunction
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Column
+import liquibase.structure.core.DataType
+import liquibase.structure.core.Relation
+import liquibase.structure.core.Table
+import liquibase.util.SqlUtil
+import liquibase.util.StringUtil
+import org.hibernate.boot.model.relational.Database
+import org.hibernate.boot.model.relational.SqlStringGenerationContext
+import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl
+import org.hibernate.boot.spi.MetadataBuildingContext
+import org.hibernate.boot.spi.MetadataImplementor
+import org.hibernate.dialect.Dialect
+import org.hibernate.dialect.PostgreSQLDialect
+import org.hibernate.generator.Generator
+import org.hibernate.id.IdentityGenerator
+import org.hibernate.id.enhanced.DatabaseStructure
+import org.hibernate.id.enhanced.SequenceStyleGenerator
+import org.hibernate.mapping.GeneratorSettings
+import org.hibernate.mapping.PersistentClass
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.SimpleValue
+import org.hibernate.type.SqlTypes
+
+/**
+ * Columns are snapshotted along with Tables in {@link TableSnapshotGenerator} but this class needs to be here to keep the default ColumnSnapshotGenerator from running.
+ * Ideally the column logic would be moved out of the TableSnapshotGenerator to better work in situations where the object types to snapshot are being controlled, but that is not the case yet.
+ */
+@CompileStatic
+class HibernateColumnSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    private static final String SQL_TIMEZONE_SUFFIX = 'with time zone'
+    private static final String LIQUIBASE_TIMEZONE_SUFFIX = 'with timezone'
+
+    private static final Pattern pattern =
+            Pattern.compile(/([^\(]*)\s*\(?\s*(\d*)?\s*,?\s*(\d*)?\s*([^\(]*?)\)?/)
+
+    HibernateColumnSnapshotGenerator() {
+        super(Column, Table)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        Column column = (Column) example
+        if (column.getType() == null) { // not the actual full version found with the table
+            if (column.getRelation() == null) {
+                throw new InvalidExampleException('No relation set on ' + column)
+            }
+            Relation relation = snapshot.get(column.getRelation())
+            if (relation != null) {
+                for (Column columnSnapshot : relation.getColumns()) {
+                    if (columnSnapshot.getName().equalsIgnoreCase(column.getName())) {
+                        return columnSnapshot
+                    }
+                }
+            }
+            snapshotColumn((Column) example, snapshot)
+            return example // did not find it
+        } else {
+            return example
+        }
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (foundObject instanceof Table) {
+            org.hibernate.mapping.Table hibernateTable = findHibernateTable(foundObject, snapshot)
+            if (hibernateTable == null) {
+                return
+            }
+
+            for (org.hibernate.mapping.Column hibernateColumn : hibernateTable.getColumns()) {
+                Column column = new Column()
+                column.setName(hibernateColumn.getName())
+                column.setRelation((Table) foundObject)
+
+                snapshotColumn(column, snapshot)
+
+                ((Table) foundObject).getColumns().add(column)
+            }
+        }
+    }
+
+    protected void snapshotColumn(Column column, DatabaseSnapshot snapshot) throws DatabaseException {
+        HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase()
+
+        org.hibernate.mapping.Table hibernateTable = findHibernateTable(column.getRelation(), snapshot)
+        if (hibernateTable == null) {
+            return
+        }
+
+        Dialect dialect = database.getDialect()
+        MetadataImplementor metadata = (MetadataImplementor) database.getMetadata()
+
+        for (org.hibernate.mapping.Column hibernateColumn : hibernateTable.getColumns()) {
+            if (hibernateColumn.getName().equalsIgnoreCase(column.getName())) {
+
+                String defaultValue = null
+                String hibernateType = hibernateColumn.getSqlType(metadata)
+
+                Matcher defaultValueMatcher =
+                        Pattern.compile(/(?i) DEFAULT\s+(.*)/).matcher(hibernateType)
+                if (defaultValueMatcher.find()) {
+                    defaultValue = defaultValueMatcher.group(1)
+                    hibernateType = hibernateType.replace(defaultValueMatcher.group(0), '')
+                }
+
+                DataType dataType = toDataType(hibernateType, hibernateColumn.getSqlTypeCode())
+                if (dataType == null) {
+                    throw new DatabaseException(
+                            'Unable to find column data type for column ' + hibernateColumn.getName())
+                }
+
+                column.setType(dataType)
+                column.setRemarks(hibernateColumn.getComment())
+
+                Integer dataTypeId = dataType.getDataTypeId()
+                boolean isEnumType = dataTypeId != null && SqlTypes.isEnumType(dataTypeId)
+
+                if (!isEnumType && hibernateColumn.getValue() instanceof SimpleValue) {
+                    DataType parseType
+                    if (DataTypeFactory.getInstance().from(dataType, database) instanceof UnknownType) {
+                        parseType = new DataType(((SimpleValue) hibernateColumn.getValue()).getTypeName())
+                    } else {
+                        parseType = dataType
+                    }
+
+                    if (defaultValue == null) {
+                        defaultValue = hibernateColumn.getDefaultValue()
+                    }
+
+                    column.setDefaultValue(SqlUtil.parseValue(snapshot.getDatabase(), defaultValue, parseType))
+                } else {
+                    column.setDefaultValue(hibernateColumn.getDefaultValue())
+                }
+                column.setNullable(hibernateColumn.isNullable())
+                column.setCertainDataType(false)
+
+                // PRIMARY KEY & AUTO-INCREMENT LOGIC (HIBERNATE 7)
+                org.hibernate.mapping.PrimaryKey hibernatePrimaryKey = hibernateTable.getPrimaryKey()
+                if (hibernatePrimaryKey != null) {
+                    boolean isPrimaryKeyColumn = false
+                    for (org.hibernate.mapping.Column pkColumn : hibernatePrimaryKey.getColumns()) {
+                        if (pkColumn.getName().equalsIgnoreCase(hibernateColumn.getName())) {
+                            isPrimaryKeyColumn = true
+                            break
+                        }
+                    }
+
+                    if (isPrimaryKeyColumn && hibernateColumn.getValue() instanceof SimpleValue) {
+                        SimpleValue simpleValue = (SimpleValue) hibernateColumn.getValue()
+                        PersistentClass persistentClass = findPersistentClass(metadata, hibernateTable)
+
+                        if (persistentClass != null) {
+                            RootClass rootClass = persistentClass.getRootClass()
+                            GeneratorSettings generatorSettings = createGeneratorSettings(simpleValue)
+                            Generator generator = simpleValue.createGenerator(dialect, rootClass, null, generatorSettings)
+
+                            if (generator != null) {
+                                boolean isAutoIncrement = false
+
+                                if (generator instanceof IdentityGenerator) {
+                                    isAutoIncrement = true
+                                } else if (generator instanceof SequenceStyleGenerator) {
+                                    SequenceStyleGenerator seqGen = (SequenceStyleGenerator) generator
+                                    if (PostgreSQLDialect.isAssignableFrom(dialect.getClass())) {
+                                        String sequenceName =
+                                                resolveSequenceName(seqGen, hibernateTable, hibernateColumn)
+                                        column.setDefaultValue(
+                                                new DatabaseFunction("nextval('" + sequenceName + "'::regclass)"))
+                                    } else if (database.supportsAutoIncrement()) {
+                                        isAutoIncrement = true
+                                    }
+                                }
+
+                                if (isAutoIncrement && database.supportsAutoIncrement()) {
+                                    column.setAutoIncrementInformation(new Column.AutoIncrementInformation())
+                                }
+                                column.setNullable(false)
+                            }
+                        }
+                    }
+                }
+                return
+            }
+        }
+    }
+
+    protected DataType toDataType(String hibernateType, Integer sqlTypeCode) {
+        Matcher matcher = pattern.matcher(hibernateType)
+        if (!matcher.matches()) {
+            return null
+        }
+
+        DataType dataType
+
+        // Small hack for enums until DataType adds support for them
+        if (sqlTypeCode != null && SqlTypes.isEnumType(sqlTypeCode)) {
+            dataType = new DataType(hibernateType)
+        } else {
+            String typeName = matcher.group(1)
+
+            // Liquibase seems to use 'with timezone' instead of 'with time zone',
+            // so we remove any 'with time zone' suffixes here.
+            // The corresponding 'with timezone' suffix will then be added below,
+            // because in that case hibernateType also ends with 'with time zone'.
+            if (typeName.toLowerCase(Locale.ROOT).endsWith(SQL_TIMEZONE_SUFFIX)) {
+                typeName = typeName.substring(0, typeName.length() - SQL_TIMEZONE_SUFFIX.length())
+                        .stripTrailing()
+            }
+
+            // If hibernateType ends with 'with time zone' we need to add the corresponding
+            // 'with timezone' suffix to the Liquibase type.
+            if (hibernateType.toLowerCase(Locale.ROOT).endsWith(SQL_TIMEZONE_SUFFIX)) {
+                typeName += (' ' + LIQUIBASE_TIMEZONE_SUFFIX)
+            }
+
+            dataType = new DataType(typeName)
+            if (matcher.group(3).isEmpty()) {
+                if (!matcher.group(2).isEmpty()) {
+                    dataType.setColumnSize(Integer.parseInt(matcher.group(2)))
+                }
+            } else {
+                dataType.setColumnSize(Integer.parseInt(matcher.group(2)))
+                dataType.setDecimalDigits(Integer.parseInt(matcher.group(3)))
+            }
+
+            String extra = StringUtil.trimToNull(matcher.group(4))
+            if (extra != null) {
+                if ('char'.equalsIgnoreCase(extra)) {
+                    dataType.setColumnSizeUnit(DataType.ColumnSizeUnit.CHAR)
+                } else {
+                    if (extra.startsWith(')')) {
+                        extra = extra.substring(1)
+                    }
+                    extra = StringUtil.trimToNull(extra.toLowerCase(Locale.ROOT).replace(SQL_TIMEZONE_SUFFIX, ''))
+                    if (extra != null) {
+                        dataType.setTypeName(dataType.getTypeName() + ' ' + extra)
+                    }
+                }
+            }
+        }
+
+        Scope.getCurrentScope()
+                .getLog(getClass())
+                .info('Converted column data type - hibernate type: ' + hibernateType + ', SQL type: ' + sqlTypeCode +
+                        ', type name: ' + dataType.getTypeName())
+
+        dataType.setDataTypeId(sqlTypeCode)
+        return dataType
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [ColumnSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+    private PersistentClass findPersistentClass(
+            MetadataImplementor metadata, org.hibernate.mapping.Table hibernateTable) {
+        for (PersistentClass persistentClass : metadata.getEntityBindings()) {
+            if (persistentClass.getTable().equals(hibernateTable)) {
+                return persistentClass
+            }
+        }
+        return null
+    }
+
+    private GeneratorSettings createGeneratorSettings(SimpleValue simpleValue) {
+        MetadataBuildingContext buildingContext = simpleValue.getBuildingContext()
+        return new GeneratorSettings() {
+
+            @Override
+            String getDefaultCatalog() {
+                return null
+            }
+
+            @Override
+            String getDefaultSchema() {
+                return null
+            }
+
+            @Override
+            SqlStringGenerationContext getSqlStringGenerationContext() {
+                Database db = buildingContext.getMetadataCollector().getDatabase()
+                return SqlStringGenerationContextImpl.fromExplicit(
+                        db.getJdbcEnvironment(), db, getDefaultCatalog(), getDefaultSchema())
+            }
+
+        }
+    }
+
+    private String resolveSequenceName(
+            SequenceStyleGenerator seqGen,
+            org.hibernate.mapping.Table hibernateTable,
+            org.hibernate.mapping.Column hibernateColumn) {
+        DatabaseStructure structure = seqGen.getDatabaseStructure()
+        if (structure.getPhysicalName() != null) {
+            return structure.getPhysicalName().render()
+        }
+        return (hibernateTable.getName() + '_' + hibernateColumn.getName() + '_seq').toLowerCase(Locale.ROOT)
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java
deleted file mode 100644
index 38197e80dc..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java
+++ /dev/null
@@ -1,310 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import java.util.Locale;
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import liquibase.Scope;
-import liquibase.datatype.DataTypeFactory;
-import liquibase.datatype.core.UnknownType;
-import liquibase.exception.DatabaseException;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.statement.DatabaseFunction;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Column;
-import liquibase.structure.core.DataType;
-import liquibase.structure.core.Relation;
-import liquibase.structure.core.Table;
-import liquibase.util.SqlUtil;
-import liquibase.util.StringUtil;
-import org.hibernate.boot.model.relational.SqlStringGenerationContext;
-import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl;
-import org.hibernate.boot.spi.MetadataImplementor;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.dialect.PostgreSQLDialect;
-import org.hibernate.mapping.GeneratorSettings;
-import org.hibernate.mapping.SimpleValue;
-import org.hibernate.type.SqlTypes;
-
-/**
- * Columns are snapshotted along with Tables in {@link TableSnapshotGenerator} but this class needs to be here to keep the default ColumnSnapshotGenerator from running.
- * Ideally the column logic would be moved out of the TableSnapshotGenerator to better work in situations where the object types to snapshot are being controlled, but that is not the case yet.
- */
-public class HibernateColumnSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    private static final String SQL_TIMEZONE_SUFFIX = "with time zone";
-    private static final String LIQUIBASE_TIMEZONE_SUFFIX = "with timezone";
-
-    private static final Pattern pattern =
-            Pattern.compile("([^\\(]*)\\s*\\(?\\s*(\\d*)?\\s*,?\\s*(\\d*)?\\s*([^\\(]*?)\\)?");
-
-    public HibernateColumnSnapshotGenerator() {
-        super(Column.class, Table.class);
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        Column column = (Column) example;
-        if (column.getType() == null) { // not the actual full version found with the table
-            if (column.getRelation() == null) {
-                throw new InvalidExampleException("No relation set on " + column);
-            }
-            Relation relation = snapshot.get(column.getRelation());
-            if (relation != null) {
-                for (Column columnSnapshot : relation.getColumns()) {
-                    if (columnSnapshot.getName().equalsIgnoreCase(column.getName())) {
-                        return columnSnapshot;
-                    }
-                }
-            }
-            snapshotColumn((Column) example, snapshot);
-            return example; // did not find it
-        } else {
-            return example;
-        }
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (foundObject instanceof Table) {
-            org.hibernate.mapping.Table hibernateTable = findHibernateTable(foundObject, snapshot);
-            if (hibernateTable == null) {
-                return;
-            }
-
-            for (org.hibernate.mapping.Column hibernateColumn : hibernateTable.getColumns()) {
-                Column column = new Column();
-                column.setName(hibernateColumn.getName());
-                column.setRelation((Table) foundObject);
-
-                snapshotColumn(column, snapshot);
-
-                ((Table) foundObject).getColumns().add(column);
-            }
-        }
-    }
-
-    @SuppressWarnings("PMD.CloseResource")
-    protected void snapshotColumn(Column column, DatabaseSnapshot snapshot) throws DatabaseException {
-        HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase();
-
-        org.hibernate.mapping.Table hibernateTable = findHibernateTable(column.getRelation(), snapshot);
-        if (hibernateTable == null) {
-            return;
-        }
-
-        Dialect dialect = database.getDialect();
-        MetadataImplementor metadata = (MetadataImplementor) database.getMetadata();
-
-        for (org.hibernate.mapping.Column hibernateColumn : hibernateTable.getColumns()) {
-            if (hibernateColumn.getName().equalsIgnoreCase(column.getName())) {
-
-                String defaultValue = null;
-                String hibernateType = hibernateColumn.getSqlType(metadata);
-
-                Matcher defaultValueMatcher =
-                        Pattern.compile("(?i) DEFAULT\\s+(.*)").matcher(hibernateType);
-                if (defaultValueMatcher.find()) {
-                    defaultValue = defaultValueMatcher.group(1);
-                    hibernateType = hibernateType.replace(defaultValueMatcher.group(0), "");
-                }
-
-                DataType dataType = toDataType(hibernateType, hibernateColumn.getSqlTypeCode());
-                if (dataType == null) {
-                    throw new DatabaseException(
-                            "Unable to find column data type for column " + hibernateColumn.getName());
-                }
-
-                column.setType(dataType);
-                column.setRemarks(hibernateColumn.getComment());
-
-                boolean isEnumType = Optional.ofNullable(dataType.getDataTypeId())
-                        .map(SqlTypes::isEnumType)
-                        .orElse(false);
-
-                if (!isEnumType && hibernateColumn.getValue() instanceof SimpleValue) {
-                    DataType parseType;
-                    if (DataTypeFactory.getInstance().from(dataType, database) instanceof UnknownType) {
-                        parseType = new DataType(((SimpleValue) hibernateColumn.getValue()).getTypeName());
-                    } else {
-                        parseType = dataType;
-                    }
-
-                    if (defaultValue == null) {
-                        defaultValue = hibernateColumn.getDefaultValue();
-                    }
-
-                    column.setDefaultValue(SqlUtil.parseValue(snapshot.getDatabase(), defaultValue, parseType));
-                } else {
-                    column.setDefaultValue(hibernateColumn.getDefaultValue());
-                }
-                column.setNullable(hibernateColumn.isNullable());
-                column.setCertainDataType(false);
-
-                // PRIMARY KEY & AUTO-INCREMENT LOGIC (HIBERNATE 7)
-                org.hibernate.mapping.PrimaryKey hibernatePrimaryKey = hibernateTable.getPrimaryKey();
-                if (hibernatePrimaryKey != null) {
-                    boolean isPrimaryKeyColumn = false;
-                    for (org.hibernate.mapping.Column pkColumn :
-                            hibernatePrimaryKey.getColumns()) {
-                        if (pkColumn.getName().equalsIgnoreCase(hibernateColumn.getName())) {
-                            isPrimaryKeyColumn = true;
-                            break;
-                        }
-                    }
-
-                    if (isPrimaryKeyColumn && hibernateColumn.getValue() instanceof SimpleValue simpleValue) {
-                        var persistentClass = findPersistentClass(metadata, hibernateTable);
-
-                        if (persistentClass != null) {
-                            var rootClass = persistentClass.getRootClass();
-                            var generatorSettings = createGeneratorSettings(simpleValue);
-                            var generator = simpleValue.createGenerator(dialect, rootClass, null, generatorSettings);
-
-                            if (generator != null) {
-                                boolean isAutoIncrement = false;
-
-                                if (generator instanceof org.hibernate.id.IdentityGenerator) {
-                                    isAutoIncrement = true;
-                                } else if (generator instanceof
-                                        org.hibernate.id.enhanced.SequenceStyleGenerator seqGen) {
-                                    if (PostgreSQLDialect.class.isAssignableFrom(dialect.getClass())) {
-                                        String sequenceName =
-                                                resolveSequenceName(seqGen, hibernateTable, hibernateColumn);
-                                        column.setDefaultValue(
-                                                new DatabaseFunction("nextval('" + sequenceName + "'::regclass)"));
-                                    } else if (database.supportsAutoIncrement()) {
-                                        isAutoIncrement = true;
-                                    }
-                                }
-
-                                if (isAutoIncrement && database.supportsAutoIncrement()) {
-                                    column.setAutoIncrementInformation(new Column.AutoIncrementInformation());
-                                }
-                                column.setNullable(false);
-                            }
-                        }
-                    }
-                }
-                return;
-            }
-        }
-    }
-
-    protected DataType toDataType(String hibernateType, Integer sqlTypeCode) {
-        Matcher matcher = pattern.matcher(hibernateType);
-        if (!matcher.matches()) {
-            return null;
-        }
-
-        DataType dataType;
-
-        // Small hack for enums until DataType adds support for them
-        if (Optional.ofNullable(sqlTypeCode).map(SqlTypes::isEnumType).orElse(false)) {
-            dataType = new DataType(hibernateType);
-        } else {
-            String typeName = matcher.group(1);
-
-            // Liquibase seems to use 'with timezone' instead of 'with time zone',
-            // so we remove any 'with time zone' suffixes here.
-            // The corresponding 'with timezone' suffix will then be added below,
-            // because in that case hibernateType also ends with 'with time zone'.
-            if (typeName.toLowerCase(Locale.ROOT).endsWith(SQL_TIMEZONE_SUFFIX)) {
-                typeName = typeName.substring(0, typeName.length() - SQL_TIMEZONE_SUFFIX.length())
-                        .stripTrailing();
-            }
-
-            // If hibernateType ends with 'with time zone' we need to add the corresponding
-            // 'with timezone' suffix to the Liquibase type.
-            if (hibernateType.toLowerCase(Locale.ROOT).endsWith(SQL_TIMEZONE_SUFFIX)) {
-                typeName += (" " + LIQUIBASE_TIMEZONE_SUFFIX);
-            }
-
-            dataType = new DataType(typeName);
-            if (matcher.group(3).isEmpty()) {
-                if (!matcher.group(2).isEmpty()) {
-                    dataType.setColumnSize(Integer.parseInt(matcher.group(2)));
-                }
-            } else {
-                dataType.setColumnSize(Integer.parseInt(matcher.group(2)));
-                dataType.setDecimalDigits(Integer.parseInt(matcher.group(3)));
-            }
-
-            String extra = StringUtil.trimToNull(matcher.group(4));
-            if (extra != null) {
-                if ("char".equalsIgnoreCase(extra)) {
-                    dataType.setColumnSizeUnit(DataType.ColumnSizeUnit.CHAR);
-                } else {
-                    if (extra.startsWith(")")) {
-                        extra = extra.substring(1);
-                    }
-                    extra = StringUtil.trimToNull(extra.toLowerCase(Locale.ROOT).replace(SQL_TIMEZONE_SUFFIX, ""));
-                    if (extra != null) {
-                        dataType.setTypeName(dataType.getTypeName() + " " + extra);
-                    }
-                }
-            }
-        }
-
-        Scope.getCurrentScope()
-                .getLog(getClass())
-                .info("Converted column data type - hibernate type: " + hibernateType + ", SQL type: " + sqlTypeCode +
-                        ", type name: " + dataType.getTypeName());
-
-        dataType.setDataTypeId(sqlTypeCode);
-        return dataType;
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.ColumnSnapshotGenerator.class};
-    }
-
-    private org.hibernate.mapping.PersistentClass findPersistentClass(
-            MetadataImplementor metadata, org.hibernate.mapping.Table hibernateTable) {
-        return metadata.getEntityBindings().stream()
-                .filter(pc -> pc.getTable().equals(hibernateTable))
-                .findFirst()
-                .orElse(null);
-    }
-
-    private GeneratorSettings createGeneratorSettings(SimpleValue simpleValue) {
-        var buildingContext = simpleValue.getBuildingContext();
-        return new GeneratorSettings() {
-            @Override
-            public String getDefaultCatalog() {
-                return null;
-            }
-
-            @Override
-            public String getDefaultSchema() {
-                return null;
-            }
-
-            @Override
-            public SqlStringGenerationContext getSqlStringGenerationContext() {
-                var db = buildingContext.getMetadataCollector().getDatabase();
-                return SqlStringGenerationContextImpl.fromExplicit(
-                        db.getJdbcEnvironment(), db, getDefaultCatalog(), getDefaultSchema());
-            }
-        };
-    }
-
-    private String resolveSequenceName(
-            org.hibernate.id.enhanced.SequenceStyleGenerator seqGen,
-            org.hibernate.mapping.Table hibernateTable,
-            org.hibernate.mapping.Column hibernateColumn) {
-        var structure = seqGen.getDatabaseStructure();
-        if (structure.getPhysicalName() != null) {
-            return structure.getPhysicalName().render();
-        }
-        return (hibernateTable.getName() + "_" + hibernateColumn.getName() + "_seq").toLowerCase(Locale.ROOT);
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.groovy
new file mode 100644
index 0000000000..1e90f81439
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.groovy
@@ -0,0 +1,85 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.exception.DatabaseException
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.ForeignKeySnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.ForeignKey
+import liquibase.structure.core.Table
+import org.hibernate.mapping.Column
+
+@CompileStatic
+class HibernateForeignKeySnapshotGenerator extends HibernateSnapshotGenerator {
+
+    HibernateForeignKeySnapshotGenerator() {
+        super(ForeignKey, Table)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        return example
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (!snapshot.getSnapshotControl().shouldInclude(ForeignKey)) {
+            return
+        }
+        if (foundObject instanceof Table) {
+            Table table = (Table) foundObject
+            org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot)
+            if (hibernateTable == null) {
+                return
+            }
+
+            for (org.hibernate.mapping.ForeignKey hibernateForeignKey : hibernateTable.getForeignKeyCollection()) {
+                if (hibernateForeignKey.isCreationEnabled()) {
+                    org.hibernate.mapping.Table hibernateReferencedTable = hibernateForeignKey.getReferencedTable()
+
+                    Table referencedTable = new Table()
+                    referencedTable.setName(hibernateReferencedTable.getName())
+                    referencedTable.setSchema(
+                            hibernateReferencedTable.getCatalog(), hibernateReferencedTable.getSchema())
+
+                    ForeignKey fk = new ForeignKey()
+                    fk.setName(hibernateForeignKey.getName())
+                    fk.setPrimaryKeyTable(referencedTable)
+                    fk.setForeignKeyTable(table)
+                    for (Column column : hibernateForeignKey.getColumns()) {
+                        fk.addForeignKeyColumn(new liquibase.structure.core.Column(column.getName()))
+                    }
+                    for (Column column : hibernateForeignKey.getReferencedColumns()) {
+                        fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(column.getName()))
+                    }
+                    if (fk.getPrimaryKeyColumns() == null ||
+                            fk.getPrimaryKeyColumns().isEmpty()) {
+                        if (hibernateReferencedTable.getPrimaryKey() != null) {
+                            for (Column column : hibernateReferencedTable.getPrimaryKey().getColumns()) {
+                                fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(column.getName()))
+                            }
+                        }
+                    }
+
+                    fk.setDeferrable(false)
+                    fk.setInitiallyDeferred(false)
+
+                    table.getOutgoingForeignKeys().add(fk)
+                    if (table.getSchema() != null) {
+                        table.getSchema().addDatabaseObject(fk)
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [ForeignKeySnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java
deleted file mode 100644
index 787cc7aa7c..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import liquibase.exception.DatabaseException;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.ForeignKey;
-import liquibase.structure.core.Table;
-import org.hibernate.mapping.Column;
-
-public class HibernateForeignKeySnapshotGenerator extends HibernateSnapshotGenerator {
-
-    public HibernateForeignKeySnapshotGenerator() {
-        super(ForeignKey.class, new Class[] {Table.class});
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        return example;
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (!snapshot.getSnapshotControl().shouldInclude(ForeignKey.class)) {
-            return;
-        }
-        if (foundObject instanceof Table table) {
-            org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot);
-            if (hibernateTable == null) {
-                return;
-            }
-
-            for (org.hibernate.mapping.ForeignKey hibernateForeignKey : hibernateTable.getForeignKeyCollection()) {
-                if (hibernateForeignKey.isCreationEnabled()) {
-                    org.hibernate.mapping.Table hibernateReferencedTable = hibernateForeignKey.getReferencedTable();
-
-                    Table referencedTable = new Table().setName(hibernateReferencedTable.getName());
-                    referencedTable.setSchema(
-                            hibernateReferencedTable.getCatalog(), hibernateReferencedTable.getSchema());
-
-                    ForeignKey fk = new ForeignKey();
-                    fk.setName(hibernateForeignKey.getName());
-                    fk.setPrimaryKeyTable(referencedTable);
-                    fk.setForeignKeyTable(table);
-                    for (Column column : hibernateForeignKey.getColumns()) {
-                        fk.addForeignKeyColumn(new liquibase.structure.core.Column(column.getName()));
-                    }
-                    for (Column column : hibernateForeignKey.getReferencedColumns()) {
-                        fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(column.getName()));
-                    }
-                    if (fk.getPrimaryKeyColumns() == null ||
-                            fk.getPrimaryKeyColumns().isEmpty()) {
-                        if (hibernateReferencedTable.getPrimaryKey() != null) {
-                            for (Column column :
-                                    hibernateReferencedTable.getPrimaryKey().getColumns()) {
-                                fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(column.getName()));
-                            }
-                        }
-                    }
-
-                    fk.setDeferrable(false);
-                    fk.setInitiallyDeferred(false);
-
-                    table.getOutgoingForeignKeys().add(fk);
-                    if (table.getSchema() != null) {
-                        table.getSchema().addDatabaseObject(fk);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.ForeignKeySnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.groovy
new file mode 100644
index 0000000000..5b4fbdcfa8
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.groovy
@@ -0,0 +1,115 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.exception.DatabaseException
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.IndexSnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Column
+import liquibase.structure.core.ForeignKey
+import liquibase.structure.core.Index
+import liquibase.structure.core.Relation
+import liquibase.structure.core.Table
+import liquibase.structure.core.UniqueConstraint
+import org.hibernate.mapping.Selectable
+
+@CompileStatic
+class HibernateIndexSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    private static final String HIBERNATE_ORDER_ASC = 'asc'
+    private static final String HIBERNATE_ORDER_DESC = 'desc'
+    private static final int SINGLE_COLUMN = 1
+
+    HibernateIndexSnapshotGenerator() {
+        super(Index, Table, ForeignKey, UniqueConstraint)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (example.getSnapshotId() != null) {
+            return example
+        }
+        Relation table = ((Index) example).getRelation()
+        org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot)
+        if (hibernateTable == null) {
+            return example
+        }
+        for (org.hibernate.mapping.Index hibernateIndex : hibernateTable.getIndexes().values()) {
+            Index index = handleHibernateIndex(table, hibernateIndex)
+            if (index.getColumnNames().equalsIgnoreCase(((Index) example).getColumnNames())) {
+                Scope.getCurrentScope().getLog(getClass()).info('Found index ' + index.getName())
+                table.getIndexes().add(index)
+                return index
+            }
+        }
+        return example
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (!snapshot.getSnapshotControl().shouldInclude(Index)) {
+            return
+        }
+        if (foundObject instanceof Table) {
+            Table table = (Table) foundObject
+            org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot)
+            if (hibernateTable == null) {
+                return
+            }
+            for (org.hibernate.mapping.Index hibernateIndex : hibernateTable.getIndexes().values()) {
+                Index index = handleHibernateIndex(table, hibernateIndex)
+                Scope.getCurrentScope().getLog(getClass()).info('Found index ' + index.getName())
+                table.getIndexes().add(index)
+            }
+        }
+    }
+
+    private Index handleHibernateIndex(Relation table, org.hibernate.mapping.Index hibernateIndex) {
+        Index index = new Index()
+        index.setRelation(table)
+        index.setName(hibernateIndex.getName())
+        index.setUnique(isUniqueIndex(hibernateIndex))
+        for (Selectable selectable : hibernateIndex.getSelectables()) {
+            org.hibernate.mapping.Column hibernateColumn = (org.hibernate.mapping.Column) selectable
+            String hibernateOrder = hibernateIndex.getSelectableOrderMap().get(hibernateColumn)
+            Boolean descending = HIBERNATE_ORDER_ASC.equals(hibernateOrder) ?
+                    Boolean.FALSE :
+                    (HIBERNATE_ORDER_DESC.equals(hibernateOrder) ? Boolean.TRUE : null)
+            index.getColumns()
+                    .add(new Column(hibernateColumn.getName())
+                            .setRelation(table)
+                            .setDescending(descending))
+        }
+        return index
+    }
+
+    private Boolean isUniqueIndex(org.hibernate.mapping.Index hibernateIndex) {
+        /*
+        This seems to be necessary to explicitly tell liquibase that there's no
+        actual diff in certain non-unique indexes
+        */
+        if (hibernateIndex.getColumnSpan() == SINGLE_COLUMN) {
+            org.hibernate.mapping.Column col = (org.hibernate.mapping.Column) hibernateIndex.getSelectables().get(0)
+            return col.isUnique()
+        } else {
+            /*
+            It seems that because Hibernate does not implement the unique property of the Jpa composite index,
+            the diff command appears 'difference', because the unique property of the entity index is 'null',
+            and the value read from the database is 'false', resulting in the generated changeSet after the Drop and
+            Recreate Index.
+            */
+            return Boolean.FALSE
+        }
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [IndexSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java
deleted file mode 100644
index c0eeed01c4..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import liquibase.Scope;
-import liquibase.exception.DatabaseException;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Column;
-import liquibase.structure.core.ForeignKey;
-import liquibase.structure.core.Index;
-import liquibase.structure.core.Relation;
-import liquibase.structure.core.Table;
-import liquibase.structure.core.UniqueConstraint;
-
-public class HibernateIndexSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    private static final String HIBERNATE_ORDER_ASC = "asc";
-    private static final String HIBERNATE_ORDER_DESC = "desc";
-    private static final int SINGLE_COLUMN = 1;
-
-    public HibernateIndexSnapshotGenerator() {
-        super(Index.class, Table.class, ForeignKey.class, UniqueConstraint.class);
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (example.getSnapshotId() != null) {
-            return example;
-        }
-        Relation table = ((Index) example).getRelation();
-        var hibernateTable = findHibernateTable(table, snapshot);
-        if (hibernateTable == null) {
-            return example;
-        }
-        for (var hibernateIndex : hibernateTable.getIndexes().values()) {
-            Index index = handleHibernateIndex(table, hibernateIndex);
-            if (index.getColumnNames().equalsIgnoreCase(((Index) example).getColumnNames())) {
-                Scope.getCurrentScope().getLog(getClass()).info("Found index " + index.getName());
-                table.getIndexes().add(index);
-                return index;
-            }
-        }
-        return example;
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (!snapshot.getSnapshotControl().shouldInclude(Index.class)) {
-            return;
-        }
-        if (foundObject instanceof Table table) {
-            var hibernateTable = findHibernateTable(table, snapshot);
-            if (hibernateTable == null) {
-                return;
-            }
-            for (var hibernateIndex : hibernateTable.getIndexes().values()) {
-                var index = handleHibernateIndex(table, hibernateIndex);
-                Scope.getCurrentScope().getLog(getClass()).info("Found index " + index.getName());
-                table.getIndexes().add(index);
-            }
-        }
-    }
-
-    private Index handleHibernateIndex(Relation table, org.hibernate.mapping.Index hibernateIndex) {
-        Index index = new Index();
-        index.setRelation(table);
-        index.setName(hibernateIndex.getName());
-        index.setUnique(isUniqueIndex(hibernateIndex));
-        for (var selectable : hibernateIndex.getSelectables()) {
-            org.hibernate.mapping.Column hibernateColumn = (org.hibernate.mapping.Column) selectable;
-            String hibernateOrder = hibernateIndex.getSelectableOrderMap().get(hibernateColumn);
-            Boolean descending = HIBERNATE_ORDER_ASC.equals(hibernateOrder) ?
-                    Boolean.FALSE :
-                    (HIBERNATE_ORDER_DESC.equals(hibernateOrder) ? Boolean.TRUE : null);
-            index.getColumns()
-                    .add(new Column(hibernateColumn.getName())
-                            .setRelation(table)
-                            .setDescending(descending));
-        }
-        return index;
-    }
-
-    private Boolean isUniqueIndex(org.hibernate.mapping.Index hibernateIndex) {
-        /*
-        This seems to be necessary to explicitly tell liquibase that there's no
-        actual diff in certain non-unique indexes
-        */
-        if (hibernateIndex.getColumnSpan() == SINGLE_COLUMN) {
-            var col = ((org.hibernate.mapping.Column)
-                    hibernateIndex.getSelectables().get(0));
-            return col.isUnique();
-        } else {
-            /*
-            It seems that because Hibernate does not implement the unique property of the Jpa composite index,
-            the diff command appears 'difference', because the unique property of the entity index is 'null',
-            and the value read from the database is 'false', resulting in the generated changeSet after the Drop and
-            Recreate Index.
-            */
-            return Boolean.FALSE;
-        }
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.IndexSnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.groovy
new file mode 100644
index 0000000000..d011f9289d
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.groovy
@@ -0,0 +1,82 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.exception.DatabaseException
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.PrimaryKeySnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Column
+import liquibase.structure.core.Index
+import liquibase.structure.core.PrimaryKey
+import liquibase.structure.core.Table
+import org.hibernate.sql.Alias
+
+@CompileStatic
+class HibernatePrimaryKeySnapshotGenerator extends HibernateSnapshotGenerator {
+
+    private static final int PK_NAME_LENGTH = 63
+    private static final String PK_NAME_SUFFIX = 'PK'
+    private static final Alias PK_NAME_ALIAS = new Alias(PK_NAME_LENGTH, PK_NAME_SUFFIX)
+
+    HibernatePrimaryKeySnapshotGenerator() {
+        super(PrimaryKey, Table)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        return example
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (!snapshot.getSnapshotControl().shouldInclude(PrimaryKey)) {
+            return
+        }
+        if (foundObject instanceof Table) {
+            Table table = (Table) foundObject
+            org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot)
+            if (hibernateTable == null) {
+                return
+            }
+            org.hibernate.mapping.PrimaryKey hibernatePrimaryKey = hibernateTable.getPrimaryKey()
+            if (hibernatePrimaryKey != null) {
+                PrimaryKey pk = new PrimaryKey()
+                String hbnTableName = hibernateTable.getName()
+
+                String pkName = PK_NAME_ALIAS.toAliasString(hbnTableName)
+                if (pkName.length() == PK_NAME_LENGTH) {
+                    String suffix =
+                            '_' + Integer.toHexString(hbnTableName.hashCode()).toUpperCase() + '_' + PK_NAME_SUFFIX
+                    pkName = pkName.substring(0, PK_NAME_LENGTH - suffix.length()) + suffix
+                }
+                pk.setName(pkName)
+
+                pk.setTable(table)
+                for (org.hibernate.mapping.Column hibernateColumn : hibernatePrimaryKey.getColumns()) {
+                    pk.getColumns().add(new Column(hibernateColumn.getName()).setRelation(table))
+                }
+
+                Scope.getCurrentScope().getLog(getClass()).info('Found primary key ' + pk.getName())
+                table.setPrimaryKey(pk)
+                Index index = new Index()
+                index.setName('IX_' + pk.getName())
+                index.setRelation(table)
+                index.setColumns(pk.getColumns())
+                index.setUnique(true)
+                pk.setBackingIndex(index)
+                table.getIndexes().add(index)
+            }
+        }
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [PrimaryKeySnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java
deleted file mode 100644
index 50bbe5f268..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import liquibase.Scope;
-import liquibase.exception.DatabaseException;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Column;
-import liquibase.structure.core.Index;
-import liquibase.structure.core.PrimaryKey;
-import liquibase.structure.core.Table;
-import org.hibernate.sql.Alias;
-
-public class HibernatePrimaryKeySnapshotGenerator extends HibernateSnapshotGenerator {
-
-    private static final int PK_NAME_LENGTH = 63;
-    private static final String PK_NAME_SUFFIX = "PK";
-    private static final Alias PK_NAME_ALIAS = new Alias(PK_NAME_LENGTH, PK_NAME_SUFFIX);
-
-    public HibernatePrimaryKeySnapshotGenerator() {
-        super(PrimaryKey.class, new Class[] {Table.class});
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        return example;
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (!snapshot.getSnapshotControl().shouldInclude(PrimaryKey.class)) {
-            return;
-        }
-        if (foundObject instanceof Table table) {
-            var hibernateTable = findHibernateTable(table, snapshot);
-            if (hibernateTable == null) {
-                return;
-            }
-            var hibernatePrimaryKey = hibernateTable.getPrimaryKey();
-            if (hibernatePrimaryKey != null) {
-                var pk = new PrimaryKey();
-                String hbnTableName = hibernateTable.getName();
-
-                String pkName = PK_NAME_ALIAS.toAliasString(hbnTableName);
-                if (pkName.length() == PK_NAME_LENGTH) {
-                    String suffix =
-                            "_" + Integer.toHexString(hbnTableName.hashCode()).toUpperCase() + "_" + PK_NAME_SUFFIX;
-                    pkName = pkName.substring(0, PK_NAME_LENGTH - suffix.length()) + suffix;
-                }
-                pk.setName(pkName);
-
-                pk.setTable(table);
-                for (org.hibernate.mapping.Column hibernateColumn : hibernatePrimaryKey.getColumns()) {
-                    pk.getColumns().add(new Column(hibernateColumn.getName()).setRelation(table));
-                }
-
-                Scope.getCurrentScope().getLog(getClass()).info("Found primary key " + pk.getName());
-                table.setPrimaryKey(pk);
-                Index index = new Index();
-                index.setName("IX_" + pk.getName());
-                index.setRelation(table);
-                index.setColumns(pk.getColumns());
-                index.setUnique(true);
-                pk.setBackingIndex(index);
-                table.getIndexes().add(index);
-            }
-        }
-    }
-
-    @Override
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.PrimaryKeySnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.groovy
new file mode 100644
index 0000000000..be503ee7ad
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.groovy
@@ -0,0 +1,44 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.exception.DatabaseException
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.SchemaSnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Catalog
+import liquibase.structure.core.Schema
+
+/**
+ * Hibernate doesn't really support Schemas, so just return the passed example back as if it had all the info it needed.
+ */
+@CompileStatic
+class HibernateSchemaSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    HibernateSchemaSnapshotGenerator() {
+        super(Schema, Catalog)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        Schema schema = new Schema(
+                snapshot.getDatabase().getDefaultCatalogName(),
+                snapshot.getDatabase().getDefaultSchemaName())
+        schema.setDefault(true)
+        return schema
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        // Nothing to do
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [SchemaSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java
deleted file mode 100644
index 6808b968d2..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import liquibase.exception.DatabaseException;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Catalog;
-import liquibase.structure.core.Schema;
-
-/**
- * Hibernate doesn't really support Schemas, so just return the passed example back as if it had all the info it needed.
- */
-public class HibernateSchemaSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    public HibernateSchemaSnapshotGenerator() {
-        super(Schema.class, new Class[] {Catalog.class});
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        return new Schema(
-                        snapshot.getDatabase().getDefaultCatalogName(),
-                        snapshot.getDatabase().getDefaultSchemaName())
-                .setDefault(true);
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        // Nothing to do
-    }
-
-    @Override
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.SchemaSnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.groovy
new file mode 100644
index 0000000000..303b66d2e2
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.groovy
@@ -0,0 +1,59 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.exception.DatabaseException
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.SequenceSnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Schema
+import liquibase.structure.core.Sequence
+import org.hibernate.boot.model.relational.Namespace
+
+/**
+ * Sequence snapshots are not yet supported, but this class needs to be implemented in order to prevent the default SequenceSnapshotGenerator from running.
+ */
+@CompileStatic
+class HibernateSequenceSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    HibernateSequenceSnapshotGenerator() {
+        super(Sequence, Schema)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        return example
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (!snapshot.getSnapshotControl().shouldInclude(Sequence)) {
+            return
+        }
+
+        if (foundObject instanceof Schema) {
+            Schema schema = (Schema) foundObject
+            HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase()
+            for (Namespace namespace : database.getMetadata().getDatabase().getNamespaces()) {
+                for (org.hibernate.boot.model.relational.Sequence sequence : namespace.getSequences()) {
+                    Sequence liquibaseSequence = new Sequence()
+                    liquibaseSequence.setName(sequence.getName().getSequenceName().getText())
+                    liquibaseSequence.setSchema(schema)
+                    liquibaseSequence.setStartValue(BigInteger.valueOf(sequence.getInitialValue()))
+                    liquibaseSequence.setIncrementBy(BigInteger.valueOf(sequence.getIncrementSize()))
+                    schema.addDatabaseObject(liquibaseSequence)
+                }
+            }
+        }
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [SequenceSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java
deleted file mode 100644
index 0aadd6e850..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import java.math.BigInteger;
-
-import liquibase.exception.DatabaseException;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Schema;
-import liquibase.structure.core.Sequence;
-
-/**
- * Sequence snapshots are not yet supported, but this class needs to be implemented in order to prevent the default SequenceSnapshotGenerator from running.
- */
-public class HibernateSequenceSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    public HibernateSequenceSnapshotGenerator() {
-        super(Sequence.class, Schema.class);
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        return example;
-    }
-
-    @Override
-    @SuppressWarnings("PMD.CloseResource")
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (!snapshot.getSnapshotControl().shouldInclude(Sequence.class)) {
-            return;
-        }
-
-        if (foundObject instanceof Schema schema) {
-            HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase();
-            for (org.hibernate.boot.model.relational.Namespace namespace :
-                    database.getMetadata().getDatabase().getNamespaces()) {
-                for (org.hibernate.boot.model.relational.Sequence sequence : namespace.getSequences()) {
-                    schema.addDatabaseObject(new Sequence()
-                            .setName(sequence.getName().getSequenceName().getText())
-                            .setSchema(schema)
-                            .setStartValue(BigInteger.valueOf(sequence.getInitialValue()))
-                            .setIncrementBy(BigInteger.valueOf(sequence.getIncrementSize())));
-                }
-            }
-        }
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.SequenceSnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.groovy
new file mode 100644
index 0000000000..34fb39155d
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.groovy
@@ -0,0 +1,136 @@
+package liquibase.ext.hibernate.snapshot
+
+import java.lang.reflect.Method
+
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.database.Database
+import liquibase.exception.DatabaseException
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.SnapshotGeneratorChain
+import liquibase.structure.DatabaseObject
+import org.hibernate.boot.Metadata
+import org.hibernate.boot.model.relational.Namespace
+import org.hibernate.boot.spi.MetadataImplementor
+import org.hibernate.mapping.Table
+
+/**
+ * Base class for all Hibernate SnapshotGenerators
+ */
+@CompileStatic
+abstract class HibernateSnapshotGenerator implements SnapshotGenerator {
+
+    private static final int PRIORITY_HIBERNATE_ADDITIONAL = 200
+    private static final int PRIORITY_HIBERNATE_DEFAULT = 100
+
+    private final Class<? extends DatabaseObject> defaultFor
+    private final Class<? extends DatabaseObject>[] addsToTypes
+
+    protected HibernateSnapshotGenerator(Class<? extends DatabaseObject> defaultFor) {
+        this(defaultFor, (Class<? extends DatabaseObject>[]) new Class[0])
+    }
+
+    @SafeVarargs
+    protected HibernateSnapshotGenerator(
+            Class<? extends DatabaseObject> defaultFor, Class<? extends DatabaseObject>... addsToTypes) {
+        this.defaultFor = defaultFor
+        this.addsToTypes = addsToTypes == null ? null : Arrays.copyOf(addsToTypes, addsToTypes.length)
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return (Class<? extends SnapshotGenerator>[]) new Class[0]
+    }
+
+    @Override
+    final int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
+        if (database instanceof HibernateDatabase) {
+            if (defaultFor != null && defaultFor.isAssignableFrom(objectType)) {
+                return PRIORITY_HIBERNATE_DEFAULT
+            }
+            Class<? extends DatabaseObject>[] types = addsTo()
+            if (types != null) {
+                for (Class<? extends DatabaseObject> type : types) {
+                    if (type.isAssignableFrom(objectType)) {
+                        return PRIORITY_HIBERNATE_ADDITIONAL
+                    }
+                }
+            }
+        }
+        return PRIORITY_NONE
+    }
+
+    @Override
+    final Class<? extends DatabaseObject>[] addsTo() {
+        return addsToTypes == null ? null : Arrays.copyOf(addsToTypes, addsToTypes.length)
+    }
+
+    @Override
+    final DatabaseObject snapshot(
+            DatabaseObject example, DatabaseSnapshot snapshot, SnapshotGeneratorChain chain)
+            throws DatabaseException, InvalidExampleException {
+        if (defaultFor != null && defaultFor.isAssignableFrom(example.getClass())) {
+            return snapshotObject(example, snapshot)
+        }
+        DatabaseObject chainResponse = chain.snapshot(example, snapshot)
+        if (chainResponse == null) {
+            return null
+        }
+        Class<? extends DatabaseObject>[] types = addsTo()
+        if (types != null) {
+            for (Class<? extends DatabaseObject> addType : types) {
+                if (addType.isAssignableFrom(example.getClass())) {
+                    addTo(chainResponse, snapshot)
+                }
+            }
+        }
+        return chainResponse
+    }
+
+    protected abstract DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException
+
+    protected abstract void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException
+
+    protected Table findHibernateTable(DatabaseObject example, DatabaseSnapshot snapshot) {
+        Metadata metadata = null
+        Database database = snapshot.getDatabase()
+        if (database instanceof HibernateDatabase) {
+            metadata = ((HibernateDatabase) database).getMetadata()
+        } else {
+            try {
+                Method getMetadata = database.getClass().getMethod('getMetadata')
+                metadata = (Metadata) getMetadata.invoke(database)
+            } catch (Exception e) {
+                Scope.getCurrentScope().getLog(getClass()).debug('Error getting metadata from database', e)
+            }
+        }
+
+        if (metadata == null) {
+            return null
+        }
+
+        MetadataImplementor metadataImplementor = (MetadataImplementor) metadata
+
+        for (Table hibernateTable : metadataImplementor.collectTableMappings()) {
+            if (hibernateTable.getName().equalsIgnoreCase(example.getName())) {
+                return hibernateTable
+            }
+        }
+
+        for (Namespace namespace : metadataImplementor.getDatabase().getNamespaces()) {
+            for (Table hibernateTable : namespace.getTables()) {
+                if (hibernateTable.getName().equalsIgnoreCase(example.getName())) {
+                    return hibernateTable
+                }
+            }
+        }
+
+        return null
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java
deleted file mode 100644
index fa75ce4805..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-import liquibase.Scope;
-import liquibase.database.Database;
-import liquibase.exception.DatabaseException;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.snapshot.SnapshotGeneratorChain;
-import liquibase.structure.DatabaseObject;
-import org.hibernate.boot.Metadata;
-import org.hibernate.boot.spi.MetadataImplementor;
-
-/**
- * Base class for all Hibernate SnapshotGenerators
- */
-public abstract class HibernateSnapshotGenerator implements SnapshotGenerator {
-
-    private static final int PRIORITY_HIBERNATE_ADDITIONAL = 200;
-    private static final int PRIORITY_HIBERNATE_DEFAULT = 100;
-
-    private final Class<? extends DatabaseObject> defaultFor;
-    private final Class<? extends DatabaseObject>[] addsToTypes;
-
-    @SuppressWarnings("unchecked")
-    protected HibernateSnapshotGenerator(Class<? extends DatabaseObject> defaultFor) {
-        this(defaultFor, (Class<? extends DatabaseObject>[]) new Class[0]);
-    }
-
-    @SafeVarargs
-    @SuppressWarnings("PMD.ArrayIsStoredDirectly")
-    protected HibernateSnapshotGenerator(
-            Class<? extends DatabaseObject> defaultFor, Class<? extends DatabaseObject>... addsToTypes) {
-        this.defaultFor = defaultFor;
-        this.addsToTypes = addsToTypes == null ? null : Arrays.copyOf(addsToTypes, addsToTypes.length);
-    }
-
-    @Override
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[0];
-    }
-
-    @Override
-    public final int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
-        if (database instanceof HibernateDatabase) {
-            if (defaultFor != null && defaultFor.isAssignableFrom(objectType)) {
-                return PRIORITY_HIBERNATE_DEFAULT;
-            }
-            Class<? extends DatabaseObject>[] types = addsTo();
-            if (types != null) {
-                for (Class<? extends DatabaseObject> type : types) {
-                    if (type.isAssignableFrom(objectType)) {
-                        return PRIORITY_HIBERNATE_ADDITIONAL;
-                    }
-                }
-            }
-        }
-        return PRIORITY_NONE;
-    }
-
-    @Override
-    @SuppressWarnings("PMD.MethodReturnsInternalArray")
-    public final Class<? extends DatabaseObject>[] addsTo() {
-        return addsToTypes == null ? null : Arrays.copyOf(addsToTypes, addsToTypes.length);
-    }
-
-    @Override
-    public final DatabaseObject snapshot(
-            DatabaseObject example, DatabaseSnapshot snapshot, SnapshotGeneratorChain chain)
-            throws DatabaseException, InvalidExampleException {
-        if (defaultFor != null && defaultFor.isAssignableFrom(example.getClass())) {
-            return snapshotObject(example, snapshot);
-        }
-        DatabaseObject chainResponse = chain.snapshot(example, snapshot);
-        if (chainResponse == null) {
-            return null;
-        }
-        Class<? extends DatabaseObject>[] types = addsTo();
-        if (types != null) {
-            for (Class<? extends DatabaseObject> addType : types) {
-                if (addType.isAssignableFrom(example.getClass())) {
-                    addTo(chainResponse, snapshot);
-                }
-            }
-        }
-        return chainResponse;
-    }
-
-    protected abstract DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException;
-
-    protected abstract void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException;
-
-    @SuppressWarnings("PMD.CloseResource")
-    protected org.hibernate.mapping.Table findHibernateTable(DatabaseObject example, DatabaseSnapshot snapshot) {
-        Metadata metadata = null;
-        Database database = snapshot.getDatabase();
-        if (database instanceof HibernateDatabase hibernateDatabase) {
-            metadata = hibernateDatabase.getMetadata();
-        } else {
-            try {
-                Method getMetadata = database.getClass().getMethod("getMetadata");
-                metadata = (Metadata) getMetadata.invoke(database);
-            } catch (Exception e) {
-                Scope.getCurrentScope().getLog(getClass()).debug("Error getting metadata from database", e);
-            }
-        }
-
-        if (metadata == null) {
-            return null;
-        }
-
-        MetadataImplementor metadataImplementor = (MetadataImplementor) metadata;
-
-        for (var hibernateTable : metadataImplementor.collectTableMappings()) {
-            if (hibernateTable.getName().equalsIgnoreCase(example.getName())) {
-                return hibernateTable;
-            }
-        }
-
-        for (var namespace : metadataImplementor.getDatabase().getNamespaces()) {
-            for (var hibernateTable : namespace.getTables()) {
-                if (hibernateTable.getName().equalsIgnoreCase(example.getName())) {
-                    return hibernateTable;
-                }
-            }
-        }
-
-        return null;
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.groovy
new file mode 100644
index 0000000000..491bad8b89
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.groovy
@@ -0,0 +1,96 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.exception.DatabaseException
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.TableSnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Schema
+import liquibase.structure.core.Table
+import org.hibernate.boot.model.relational.Namespace
+import org.hibernate.boot.spi.MetadataImplementor
+import org.hibernate.mapping.ForeignKey
+
+@CompileStatic
+class HibernateTableSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    HibernateTableSnapshotGenerator() {
+        super(Table, Schema)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (example.getSnapshotId() != null) {
+            return example
+        }
+        org.hibernate.mapping.Table hibernateTable = findHibernateTable(example, snapshot)
+        if (hibernateTable == null) {
+            return example
+        }
+
+        Table table = new Table()
+        table.setName(hibernateTable.getName())
+        Scope.getCurrentScope().getLog(getClass()).info('Found table ' + table.getName())
+        table.setSchema(example.getSchema())
+        if (hibernateTable.getComment() != null && !hibernateTable.getComment().isEmpty()) {
+            table.setRemarks(hibernateTable.getComment())
+        }
+
+        return table
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (!snapshot.getSnapshotControl().shouldInclude(Table)) {
+            return
+        }
+
+        if (foundObject instanceof Schema) {
+            Schema schema = (Schema) foundObject
+
+            HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase()
+            MetadataImplementor metadata = (MetadataImplementor) database.getMetadata()
+
+            // Hibernate 7: GenerationType.TABLE tables are not visible via getEntityBindings(),
+            // so we retrieve tables from namespaces instead.
+            for (Namespace namespace : metadata.getDatabase().getNamespaces()) {
+                for (org.hibernate.mapping.Table hibernateTable : namespace.getTables()) {
+                    if (hibernateTable.isPhysicalTable()) {
+                        addDatabaseObjectToSchema(hibernateTable, schema, snapshot)
+                        for (ForeignKey fk : hibernateTable.getForeignKeyCollection()) {
+                            addDatabaseObjectToSchema(fk.getTable(), schema, snapshot)
+                        }
+                    }
+                }
+            }
+
+            for (org.hibernate.mapping.Collection coll : metadata.getCollectionBindings()) {
+                org.hibernate.mapping.Table hTable = coll.getCollectionTable()
+                if (hTable.isPhysicalTable()) {
+                    addDatabaseObjectToSchema(hTable, schema, snapshot)
+                }
+            }
+        }
+    }
+
+    private void addDatabaseObjectToSchema(org.hibernate.mapping.Table join, Schema schema, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        Table joinTable = new Table()
+        joinTable.setName(join.getName())
+        joinTable.setSchema(schema)
+        Scope.getCurrentScope().getLog(getClass()).info('Found table ' + joinTable.getName())
+        schema.addDatabaseObject(snapshotObject(joinTable, snapshot))
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [TableSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java
deleted file mode 100644
index 850c90e636..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import liquibase.Scope;
-import liquibase.exception.DatabaseException;
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Schema;
-import liquibase.structure.core.Table;
-import org.hibernate.boot.model.relational.Namespace;
-import org.hibernate.boot.spi.MetadataImplementor;
-import org.hibernate.mapping.ForeignKey;
-
-public class HibernateTableSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    public HibernateTableSnapshotGenerator() {
-        super(Table.class, Schema.class);
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (example.getSnapshotId() != null) {
-            return example;
-        }
-        org.hibernate.mapping.Table hibernateTable = findHibernateTable(example, snapshot);
-        if (hibernateTable == null) {
-            return example;
-        }
-
-        Table table = new Table().setName(hibernateTable.getName());
-        Scope.getCurrentScope().getLog(getClass()).info("Found table " + table.getName());
-        table.setSchema(example.getSchema());
-        if (hibernateTable.getComment() != null && !hibernateTable.getComment().isEmpty()) {
-            table.setRemarks(hibernateTable.getComment());
-        }
-
-        return table;
-    }
-
-    @Override
-    @SuppressWarnings("PMD.CloseResource")
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (!snapshot.getSnapshotControl().shouldInclude(Table.class)) {
-            return;
-        }
-
-        if (foundObject instanceof Schema schema) {
-
-            var database = (HibernateDatabase) snapshot.getDatabase();
-            var metadata = (MetadataImplementor) database.getMetadata();
-
-            // Hibernate 7: GenerationType.TABLE tables are not visible via getEntityBindings(),
-            // so we retrieve tables from namespaces instead.
-            for (Namespace namespace : metadata.getDatabase().getNamespaces()) {
-                for (org.hibernate.mapping.Table hibernateTable : namespace.getTables()) {
-                    if (hibernateTable.isPhysicalTable()) {
-                        addDatabaseObjectToSchema(hibernateTable, schema, snapshot);
-                        for (ForeignKey fk : hibernateTable.getForeignKeyCollection()) {
-                            addDatabaseObjectToSchema(fk.getTable(), schema, snapshot);
-                        }
-                    }
-                }
-            }
-
-            for (org.hibernate.mapping.Collection coll : metadata.getCollectionBindings()) {
-                org.hibernate.mapping.Table hTable = coll.getCollectionTable();
-                if (hTable.isPhysicalTable()) {
-                    addDatabaseObjectToSchema(hTable, schema, snapshot);
-                }
-            }
-        }
-    }
-
-    private void addDatabaseObjectToSchema(org.hibernate.mapping.Table join, Schema schema, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        Table joinTable = new Table().setName(join.getName());
-        joinTable.setSchema(schema);
-        Scope.getCurrentScope().getLog(getClass()).info("Found table " + joinTable.getName());
-        schema.addDatabaseObject(snapshotObject(joinTable, snapshot));
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.TableSnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.groovy
new file mode 100644
index 0000000000..92e013c452
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.groovy
@@ -0,0 +1,142 @@
+package liquibase.ext.hibernate.snapshot
+
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.exception.DatabaseException
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.UniqueConstraintSnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Column
+import liquibase.structure.core.Index
+import liquibase.structure.core.Table
+import liquibase.structure.core.UniqueConstraint
+import org.hibernate.HibernateException
+import org.hibernate.mapping.UniqueKey
+
+@CompileStatic
+class HibernateUniqueConstraintSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    private static final int MAX_NAME_LENGTH = 64
+    private static final int SHORTENED_NAME_LENGTH = 63
+    private static final int START_INDEX = 0
+    private static final int FIRST_COLUMN = 0
+    private static final String SEARCH_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+    private static final Random RANDOM = new Random()
+
+    HibernateUniqueConstraintSnapshotGenerator() {
+        super(UniqueConstraint, Table)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        return example
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        if (!snapshot.getSnapshotControl().shouldInclude(UniqueConstraint)) {
+            return
+        }
+
+        if (foundObject instanceof Table) {
+            Table table = (Table) foundObject
+            org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot)
+            if (hibernateTable == null) {
+                return
+            }
+            for (UniqueKey hibernateUnique : hibernateTable.getUniqueKeys().values()) {
+                UniqueConstraint uniqueConstraint = new UniqueConstraint()
+                uniqueConstraint.setName(hibernateUnique.getName())
+                uniqueConstraint.setRelation(table)
+                uniqueConstraint.setClustered(Boolean.FALSE) // No way to set true via Hibernate
+
+                int i = 0
+                for (org.hibernate.mapping.Column hibernateColumn : hibernateUnique.getColumns()) {
+                    uniqueConstraint.addColumn(i++, new Column(hibernateColumn.getName()).setRelation(table))
+                }
+
+                Index index = getBackingIndex(uniqueConstraint, hibernateTable, snapshot)
+                uniqueConstraint.setBackingIndex(index)
+
+                Scope.getCurrentScope().getLog(getClass()).info('Found unique constraint ' + uniqueConstraint)
+                table.getUniqueConstraints().add(uniqueConstraint)
+            }
+            for (org.hibernate.mapping.Column column : hibernateTable.getColumns()) {
+                if (column.isUnique()) {
+                    UniqueConstraint uniqueConstraint = new UniqueConstraint()
+                    uniqueConstraint.setRelation(table)
+                    uniqueConstraint.setClustered(Boolean.FALSE) // No way to set true via Hibernate
+                    String name = 'UC_' + table.getName().toUpperCase(Locale.ROOT) +
+                            column.getName().toUpperCase(Locale.ROOT) + '_COL'
+                    if (name.length() > MAX_NAME_LENGTH) {
+                        name = name.substring(START_INDEX, SHORTENED_NAME_LENGTH)
+                    }
+                    uniqueConstraint.addColumn(FIRST_COLUMN, new Column(column.getName()).setRelation(table))
+                    uniqueConstraint.setName(name)
+                    Scope.getCurrentScope().getLog(getClass()).info('Found unique constraint ' + uniqueConstraint)
+                    table.getUniqueConstraints().add(uniqueConstraint)
+
+                    Index index = getBackingIndex(uniqueConstraint, hibernateTable, snapshot)
+                    uniqueConstraint.setBackingIndex(index)
+                }
+            }
+
+            for (UniqueConstraint uc : table.getUniqueConstraints()) {
+                if (uc.getName() == null || uc.getName().isEmpty()) {
+                    String name = table.getName() + uc.getColumnNames()
+                    name = 'UCIDX' + hashedName(name)
+                    uc.setName(name)
+                }
+            }
+        }
+    }
+
+    private String hashedName(String s) {
+        try {
+            MessageDigest md = MessageDigest.getInstance('MD5')
+            md.reset()
+            md.update(s.getBytes(StandardCharsets.UTF_8))
+            byte[] digest = md.digest()
+            BigInteger bigInt = new BigInteger(1, digest)
+            // By converting to base 35 (full alphanumeric), we guarantee
+            // that the length of the name will always be smaller than the 30
+            // character identifier restriction enforced by a few dialects.
+            return bigInt.toString(35)
+        } catch (NoSuchAlgorithmException e) {
+            throw new HibernateException('Unable to generate a hashed name!', e)
+        }
+    }
+
+    protected Index getBackingIndex(
+            UniqueConstraint uniqueConstraint, org.hibernate.mapping.Table hibernateTable, DatabaseSnapshot snapshot) {
+        Index index = new Index()
+        index.setRelation(uniqueConstraint.getRelation())
+        index.setColumns(uniqueConstraint.getColumns())
+        index.setUnique(true)
+        index.setName(String.format('%s_%s_IX', hibernateTable.getName(), randomIdentifier(4)))
+
+        return index
+    }
+
+    private String randomIdentifier(int len) {
+        StringBuilder sb = new StringBuilder(len)
+        for (int i = 0; i < len; i++) {
+            sb.append(SEARCH_CHARS.charAt(RANDOM.nextInt(SEARCH_CHARS.length())))
+        }
+        return sb.toString()
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [UniqueConstraintSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java
deleted file mode 100644
index dfe3cae12a..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Locale;
-import java.util.Random;
-
-import liquibase.Scope;
-import liquibase.exception.DatabaseException;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Column;
-import liquibase.structure.core.Index;
-import liquibase.structure.core.Table;
-import liquibase.structure.core.UniqueConstraint;
-import org.hibernate.HibernateException;
-
-public class HibernateUniqueConstraintSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    private static final int MAX_NAME_LENGTH = 64;
-    private static final int SHORTENED_NAME_LENGTH = 63;
-    private static final int START_INDEX = 0;
-    private static final int FIRST_COLUMN = 0;
-    private static final String SEARCH_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-    private static final Random RANDOM = new Random();
-
-    public HibernateUniqueConstraintSnapshotGenerator() {
-        super(UniqueConstraint.class, Table.class);
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        return example;
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        if (!snapshot.getSnapshotControl().shouldInclude(UniqueConstraint.class)) {
-            return;
-        }
-
-        if (foundObject instanceof Table table) {
-            var hibernateTable = findHibernateTable(table, snapshot);
-            if (hibernateTable == null) {
-                return;
-            }
-            for (var hibernateUnique : hibernateTable.getUniqueKeys().values()) {
-                var uniqueConstraint = new UniqueConstraint();
-                uniqueConstraint.setName(hibernateUnique.getName());
-                uniqueConstraint.setRelation(table);
-                uniqueConstraint.setClustered(Boolean.FALSE); // No way to set true via Hibernate
-
-                int i = 0;
-                for (var hibernateColumn : hibernateUnique.getColumns()) {
-                    uniqueConstraint.addColumn(i++, new Column(hibernateColumn.getName()).setRelation(table));
-                }
-
-                Index index = getBackingIndex(uniqueConstraint, hibernateTable, snapshot);
-                uniqueConstraint.setBackingIndex(index);
-
-                Scope.getCurrentScope().getLog(getClass()).info("Found unique constraint " + uniqueConstraint);
-                table.getUniqueConstraints().add(uniqueConstraint);
-            }
-            for (var column : hibernateTable.getColumns()) {
-                if (column.isUnique()) {
-                    UniqueConstraint uniqueConstraint = new UniqueConstraint();
-                    uniqueConstraint.setRelation(table);
-                    uniqueConstraint.setClustered(Boolean.FALSE); // No way to set true via Hibernate
-                    String name = "UC_" + table.getName().toUpperCase(Locale.ROOT) +
-                            column.getName().toUpperCase(Locale.ROOT) + "_COL";
-                    if (name.length() > MAX_NAME_LENGTH) {
-                        name = name.substring(START_INDEX, SHORTENED_NAME_LENGTH);
-                    }
-                    uniqueConstraint.addColumn(FIRST_COLUMN, new Column(column.getName()).setRelation(table));
-                    uniqueConstraint.setName(name);
-                    Scope.getCurrentScope().getLog(getClass()).info("Found unique constraint " + uniqueConstraint);
-                    table.getUniqueConstraints().add(uniqueConstraint);
-
-                    Index index = getBackingIndex(uniqueConstraint, hibernateTable, snapshot);
-                    uniqueConstraint.setBackingIndex(index);
-                }
-            }
-
-            for (UniqueConstraint uc : table.getUniqueConstraints()) {
-                if (uc.getName() == null || uc.getName().isEmpty()) {
-                    String name = table.getName() + uc.getColumnNames();
-                    name = "UCIDX" + hashedName(name);
-                    uc.setName(name);
-                }
-            }
-        }
-    }
-
-    private String hashedName(String s) {
-        try {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            md.reset();
-            md.update(s.getBytes(StandardCharsets.UTF_8));
-            byte[] digest = md.digest();
-            BigInteger bigInt = new BigInteger(1, digest);
-            // By converting to base 35 (full alphanumeric), we guarantee
-            // that the length of the name will always be smaller than the 30
-            // character identifier restriction enforced by a few dialects.
-            return bigInt.toString(35);
-        } catch (NoSuchAlgorithmException e) {
-            throw new HibernateException("Unable to generate a hashed name!", e);
-        }
-    }
-
-    protected Index getBackingIndex(
-            UniqueConstraint uniqueConstraint, org.hibernate.mapping.Table hibernateTable, DatabaseSnapshot snapshot) {
-        Index index = new Index();
-        index.setRelation(uniqueConstraint.getRelation());
-        index.setColumns(uniqueConstraint.getColumns());
-        index.setUnique(true);
-        index.setName(String.format("%s_%s_IX", hibernateTable.getName(), randomIdentifier(4)));
-
-        return index;
-    }
-
-    private String randomIdentifier(int len) {
-        StringBuilder sb = new StringBuilder(len);
-        for (int i = 0; i < len; i++) {
-            sb.append(SEARCH_CHARS.charAt(RANDOM.nextInt(SEARCH_CHARS.length())));
-        }
-        return sb.toString();
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.UniqueConstraintSnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.groovy
new file mode 100644
index 0000000000..da5ea41ec0
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.groovy
@@ -0,0 +1,40 @@
+package liquibase.ext.hibernate.snapshot
+
+import groovy.transform.CompileStatic
+import liquibase.exception.DatabaseException
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.InvalidExampleException
+import liquibase.snapshot.SnapshotGenerator
+import liquibase.snapshot.jvm.ViewSnapshotGenerator
+import liquibase.structure.DatabaseObject
+import liquibase.structure.core.Schema
+import liquibase.structure.core.View
+
+/**
+ * View snapshots are not supported from hibernate, but this class needs to be implemented in order to prevent the default ViewSnapshotGenerator from running.
+ */
+@CompileStatic
+class HibernateViewSnapshotGenerator extends HibernateSnapshotGenerator {
+
+    HibernateViewSnapshotGenerator() {
+        super(View, Schema)
+    }
+
+    @Override
+    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        throw new DatabaseException('No views in Hibernate mapping')
+    }
+
+    @Override
+    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
+            throws DatabaseException, InvalidExampleException {
+        // No views in Hibernate mapping
+    }
+
+    @Override
+    Class<? extends SnapshotGenerator>[] replaces() {
+        return [ViewSnapshotGenerator] as Class<? extends SnapshotGenerator>[]
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java
deleted file mode 100644
index 99655cc34c..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package liquibase.ext.hibernate.snapshot;
-
-import liquibase.exception.DatabaseException;
-import liquibase.snapshot.DatabaseSnapshot;
-import liquibase.snapshot.InvalidExampleException;
-import liquibase.snapshot.SnapshotGenerator;
-import liquibase.structure.DatabaseObject;
-import liquibase.structure.core.Schema;
-import liquibase.structure.core.View;
-
-/**
- * View snapshots are not supported from hibernate, but this class needs to be implemented in order to prevent the default ViewSnapshotGenerator from running.
- */
-public class HibernateViewSnapshotGenerator extends HibernateSnapshotGenerator {
-
-    public HibernateViewSnapshotGenerator() {
-        super(View.class, new Class[] {Schema.class});
-    }
-
-    @Override
-    protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        throw new DatabaseException("No views in Hibernate mapping");
-    }
-
-    @Override
-    protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot)
-            throws DatabaseException, InvalidExampleException {
-        // No views in Hibernate mapping
-    }
-
-    @Override
-    public Class<? extends SnapshotGenerator>[] replaces() {
-        return new Class[] {liquibase.snapshot.jvm.ViewSnapshotGenerator.class};
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.groovy
new file mode 100644
index 0000000000..a1a15460f1
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.groovy
@@ -0,0 +1,12 @@
+package liquibase.ext.hibernate.snapshot.extension
+
+import groovy.transform.CompileStatic
+
+@CompileStatic
+interface ExtendedSnapshotGenerator<T, U> {
+
+    U snapshot(T object)
+
+    boolean supports(T object)
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java
deleted file mode 100644
index f4eada82d5..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package liquibase.ext.hibernate.snapshot.extension;
-
-public interface ExtendedSnapshotGenerator<T, U> {
-
-    U snapshot(T object);
-
-    boolean supports(T object);
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.groovy
new file mode 100644
index 0000000000..dac0f51f87
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.groovy
@@ -0,0 +1,54 @@
+package liquibase.ext.hibernate.snapshot.extension
+
+import groovy.transform.CompileStatic
+import liquibase.structure.core.Column
+import liquibase.structure.core.DataType
+import liquibase.structure.core.PrimaryKey
+import liquibase.structure.core.Table
+import org.hibernate.generator.Generator
+import org.hibernate.id.enhanced.TableGenerator
+
+@CompileStatic
+class TableGeneratorSnapshotGenerator implements ExtendedSnapshotGenerator<Generator, Table> {
+
+    private static final String PK_DATA_TYPE = 'varchar'
+    private static final String VALUE_DATA_TYPE = 'bigint'
+
+    @Override
+    Table snapshot(Generator ig) {
+        TableGenerator tableGenerator = (TableGenerator) ig
+        Table table = new Table()
+        table.setName(tableGenerator.getTableName())
+
+        Column pkColumn = new Column()
+        pkColumn.setName(tableGenerator.getSegmentColumnName())
+        DataType pkDataType = new DataType(PK_DATA_TYPE)
+        pkDataType.setColumnSize(tableGenerator.getSegmentValueLength())
+        pkColumn.setType(pkDataType)
+        pkColumn.setCertainDataType(false)
+        pkColumn.setRelation(table)
+        table.getColumns().add(pkColumn)
+
+        PrimaryKey primaryKey = new PrimaryKey()
+        primaryKey.setName(tableGenerator.getTableName() + 'PK')
+        primaryKey.addColumn(0, new Column(pkColumn.getName()).setRelation(table))
+        primaryKey.setTable(table)
+        table.setPrimaryKey(primaryKey)
+
+        Column valueColumn = new Column()
+        valueColumn.setName(tableGenerator.getValueColumnName())
+        valueColumn.setType(new DataType(VALUE_DATA_TYPE))
+        valueColumn.setNullable(false)
+        valueColumn.setCertainDataType(false)
+        valueColumn.setRelation(table)
+        table.getColumns().add(valueColumn)
+
+        return table
+    }
+
+    @Override
+    boolean supports(Generator ig) {
+        return ig instanceof TableGenerator
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java
deleted file mode 100644
index ae5261979d..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package liquibase.ext.hibernate.snapshot.extension;
-
-import liquibase.structure.core.Column;
-import liquibase.structure.core.DataType;
-import liquibase.structure.core.PrimaryKey;
-import liquibase.structure.core.Table;
-import org.hibernate.generator.Generator;
-import org.hibernate.id.enhanced.TableGenerator;
-
-public class TableGeneratorSnapshotGenerator implements ExtendedSnapshotGenerator<Generator, Table> {
-
-    private static final String PK_DATA_TYPE = "varchar";
-    private static final String VALUE_DATA_TYPE = "bigint";
-
-    @Override
-    public Table snapshot(Generator ig) {
-        TableGenerator tableGenerator = (TableGenerator) ig;
-        Table table = new Table().setName(tableGenerator.getTableName());
-
-        Column pkColumn = new Column();
-        pkColumn.setName(tableGenerator.getSegmentColumnName());
-        DataType pkDataType = new DataType(PK_DATA_TYPE);
-        pkDataType.setColumnSize(tableGenerator.getSegmentValueLength());
-        pkColumn.setType(pkDataType);
-        pkColumn.setCertainDataType(false);
-        pkColumn.setRelation(table);
-        table.getColumns().add(pkColumn);
-
-        PrimaryKey primaryKey = new PrimaryKey();
-        primaryKey.setName(tableGenerator.getTableName() + "PK");
-        primaryKey.addColumn(0, new Column(pkColumn.getName()).setRelation(table));
-        primaryKey.setTable(table);
-        table.setPrimaryKey(primaryKey);
-
-        Column valueColumn = new Column();
-        valueColumn.setName(tableGenerator.getValueColumnName());
-        valueColumn.setType(new DataType(VALUE_DATA_TYPE));
-        valueColumn.setNullable(false);
-        valueColumn.setCertainDataType(false);
-        valueColumn.setRelation(table);
-        table.getColumns().add(valueColumn);
-
-        return table;
-    }
-
-    @Override
-    public boolean supports(Generator ig) {
-        return ig instanceof TableGenerator;
-    }
-}


(grails-core) 01/14: Convert the forked bson/json reader, scanner, writer and token classes to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit df6135213cb7845d136a648e15e14abd7ee54eff
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 16:40:39 2026 -0500

    Convert the forked bson/json reader, scanner, writer and token classes to Groovy
    
    Fourth grails-data-mongodb batch: JsonReader, JsonScanner, JsonToken,
    JsonTokenType and JsonWriter, the org.bson.json fork. The MongoDB, Inc.
    copyright headers are preserved verbatim.
    
    JsonScanner's character switches are rewritten as if-chains on primitive
    comparisons: the scanner works on int character codes (so -1 can mark end
    of input) and switches them against char constants, which Java resolves
    by numeric promotion but a Groovy switch matches with equals(), where a
    Character never equals an Integer and every case would silently fall to
    default. The enum-driven state switches are kept as switches. JsonWriter's
    Unicode-category switch is likewise a comparison chain (Character.getType
    returns an int, the category constants are bytes).
    
    The inner Context and Mark classes extend inner classes of org.bson's
    AbstractBsonReader/AbstractBsonWriter, whose constructors take the
    enclosing reader/writer as a hidden first argument that javac supplies
    implicitly; Groovy needs it passed explicitly as JsonReader.this /
    JsonWriter.this. JsonReader.Context keeps its getContextType() override
    (with a suppression) because it is what makes org.bson's protected method
    reachable from the reader; the outer fields the inner classes read are
    @PackageScope; and the write-only `mark` field is dropped.
    
    Verified: compileGroovy, codenarc and checkstyle clean; bson suite
    identical to baseline (33 tests, 0 failures), including JsonReaderSpec.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 .../grails/datastore/bson/json/JsonReader.groovy   | 529 ++++++++++++++++++++
 .../org/grails/datastore/bson/json/JsonReader.java | 509 --------------------
 .../grails/datastore/bson/json/JsonScanner.groovy  | 472 ++++++++++++++++++
 .../grails/datastore/bson/json/JsonScanner.java    | 532 ---------------------
 .../grails/datastore/bson/json/JsonToken.groovy    |  78 +++
 .../org/grails/datastore/bson/json/JsonToken.java  |  72 ---
 .../{JsonTokenType.java => JsonTokenType.groovy}   |   9 +-
 .../grails/datastore/bson/json/JsonWriter.groovy   | 448 +++++++++++++++++
 .../org/grails/datastore/bson/json/JsonWriter.java | 443 -----------------
 9 files changed, 1535 insertions(+), 1557 deletions(-)

diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.groovy b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.groovy
new file mode 100644
index 0000000000..2db09ab0c6
--- /dev/null
+++ b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.groovy
@@ -0,0 +1,529 @@
+/*
+ * Copyright (c) 2008-2014 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.grails.datastore.bson.json
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.bson.AbstractBsonReader
+import org.bson.AbstractBsonReader.State
+import org.bson.BsonBinary
+import org.bson.BsonContextType
+import org.bson.BsonDbPointer
+import org.bson.BsonInvalidOperationException
+import org.bson.BsonReaderMark
+import org.bson.BsonRegularExpression
+import org.bson.BsonTimestamp
+import org.bson.BsonType
+import org.bson.json.JsonParseException
+import org.bson.types.Decimal128
+import org.bson.types.ObjectId
+
+/**
+ * A simplified fork of {@link org.bson.json.JsonReader} that works with readers and removes processing related to MongoDB
+ *
+ * @author Graeme Rocher
+ * @since 6.0
+ */
+@CompileStatic
+class JsonReader extends AbstractBsonReader {
+
+    @PackageScope final JsonScanner scanner
+    @PackageScope JsonToken pushedToken
+    @PackageScope Object currentValue
+
+    /**
+     * Constructs a new instance with the given JSON string.
+     *
+     * @param json     A string representation of a JSON.
+     */
+    JsonReader(final String json) {
+        this(new StringReader(json))
+    }
+
+    /**
+     * Constructs a new instance with the given JSON reader.
+     *
+     * @param reader The reader.
+     */
+    JsonReader(final Reader reader) {
+        super()
+        scanner = new JsonScanner(reader)
+        setContext(new Context(null, BsonContextType.TOP_LEVEL))
+    }
+
+    @Override
+    protected BsonBinary doReadBinaryData() {
+        return (BsonBinary) currentValue
+    }
+
+    @Override
+    protected byte doPeekBinarySubType() {
+        return doReadBinaryData().getType()
+    }
+
+    @Override
+    protected int doPeekBinarySize() {
+        return doReadBinaryData().getData().length
+    }
+
+    @Override
+    protected boolean doReadBoolean() {
+        return (Boolean) currentValue
+    }
+
+    @Override
+    BsonType readBsonType() {
+        if (isClosed()) {
+            throw new IllegalStateException('This instance has been closed')
+        }
+        if (getState() == State.INITIAL || getState() == State.DONE || getState() == State.SCOPE_DOCUMENT) {
+            // in JSON the top level value can be of any type so fall through
+            setState(State.TYPE)
+        }
+        if (getState() != State.TYPE) {
+            throwInvalidState('readBSONType', State.TYPE)
+        }
+
+        if (getContext().getContextType() == BsonContextType.DOCUMENT) {
+            JsonToken nameToken = popToken()
+            switch (nameToken.getType()) {
+                case JsonTokenType.STRING:
+                case JsonTokenType.UNQUOTED_STRING:
+                    setCurrentName(nameToken.getValue(String))
+                    break
+                case JsonTokenType.END_OBJECT:
+                    setState(State.END_OF_DOCUMENT)
+                    return BsonType.END_OF_DOCUMENT
+                default:
+                    throw new JsonParseException("JSON reader was expecting a name but found '%s'.", nameToken.getValue())
+            }
+
+            JsonToken colonToken = popToken()
+            if (colonToken.getType() != JsonTokenType.COLON) {
+                throw new JsonParseException("JSON reader was expecting ':' but found '%s'.", colonToken.getValue())
+            }
+        }
+
+        JsonToken token = popToken()
+        if (getContext().getContextType() == BsonContextType.ARRAY && token.getType() == JsonTokenType.END_ARRAY) {
+            setState(State.END_OF_ARRAY)
+            return BsonType.END_OF_DOCUMENT
+        }
+
+        boolean noValueFound = false
+        switch (token.getType()) {
+            case JsonTokenType.BEGIN_ARRAY:
+                setCurrentBsonType(BsonType.ARRAY)
+                break
+            case JsonTokenType.BEGIN_OBJECT:
+                visitExtendedJSON()
+                break
+            case JsonTokenType.DOUBLE:
+                setCurrentBsonType(BsonType.DOUBLE)
+                currentValue = token.getValue()
+                break
+            case JsonTokenType.END_OF_FILE:
+                setCurrentBsonType(BsonType.END_OF_DOCUMENT)
+                break
+            case JsonTokenType.INT32:
+                setCurrentBsonType(BsonType.INT32)
+                currentValue = token.getValue()
+                break
+            case JsonTokenType.INT64:
+                setCurrentBsonType(BsonType.INT64)
+                currentValue = token.getValue()
+                break
+            case JsonTokenType.REGULAR_EXPRESSION:
+                setCurrentBsonType(BsonType.REGULAR_EXPRESSION)
+                currentValue = token.getValue()
+                break
+            case JsonTokenType.STRING:
+                setCurrentBsonType(BsonType.STRING)
+                currentValue = token.getValue()
+                break
+            case JsonTokenType.UNQUOTED_STRING:
+                String value = token.getValue(String)
+
+                if (JsonToken.BOOLEAN_FALSE == value || JsonToken.BOOLEAN_TRUE == value) {
+                    setCurrentBsonType(BsonType.BOOLEAN)
+                    currentValue = Boolean.parseBoolean(value)
+                } else if ('Infinity' == value) {
+                    setCurrentBsonType(BsonType.DOUBLE)
+                    currentValue = Double.POSITIVE_INFINITY
+                } else if ('NaN' == value) {
+                    setCurrentBsonType(BsonType.DOUBLE)
+                    currentValue = Double.NaN
+                } else if (JsonToken.NULL == value) {
+                    setCurrentBsonType(BsonType.NULL)
+                } else if ('undefined' == value) {
+                    setCurrentBsonType(BsonType.UNDEFINED)
+                } else {
+                    noValueFound = true
+                }
+                break
+            default:
+                noValueFound = true
+                break
+        }
+        if (noValueFound) {
+            throw new JsonParseException("JSON reader was expecting a value but found '%s'.", token.getValue())
+        }
+
+        if (getContext().getContextType() == BsonContextType.ARRAY || getContext().getContextType() == BsonContextType.DOCUMENT) {
+            JsonToken commaToken = popToken()
+            if (commaToken.getType() != JsonTokenType.COMMA) {
+                pushToken(commaToken)
+            }
+        }
+
+        switch (getContext().getContextType()) {
+            case BsonContextType.ARRAY:
+            case BsonContextType.JAVASCRIPT_WITH_SCOPE:
+            case BsonContextType.TOP_LEVEL:
+                setState(State.VALUE)
+                break
+            default:
+                // DOCUMENT, SCOPE_DOCUMENT and anything else
+                setState(State.NAME)
+                break
+        }
+        return getCurrentBsonType()
+    }
+
+    @Override
+    protected long doReadDateTime() {
+        return (Long) currentValue
+    }
+
+    @Override
+    protected double doReadDouble() {
+        return (Double) currentValue
+    }
+
+    @Override
+    protected void doReadEndArray() {
+        setContext(getContext().getParentContext())
+
+        if (getContext().getContextType() == BsonContextType.ARRAY || getContext().getContextType() == BsonContextType.DOCUMENT) {
+            JsonToken commaToken = popToken()
+            if (commaToken.getType() != JsonTokenType.COMMA) {
+                pushToken(commaToken)
+            }
+        }
+    }
+
+    @Override
+    protected void doReadEndDocument() {
+        setContext(getContext().getParentContext())
+        if (getContext() != null && getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) {
+            setContext(getContext().getParentContext()) // JavaScriptWithScope
+            verifyToken(JsonToken.CLOSE_BRACE) // outermost closing bracket for JavaScriptWithScope
+        }
+
+        if (getContext() == null) {
+            throw new JsonParseException('Unexpected end of document.')
+        }
+
+        if (getContext().getContextType() == BsonContextType.ARRAY || getContext().getContextType() == BsonContextType.DOCUMENT) {
+            JsonToken commaToken = popToken()
+            if (commaToken.getType() != JsonTokenType.COMMA) {
+                pushToken(commaToken)
+            }
+        }
+    }
+
+    @Override
+    protected int doReadInt32() {
+        return (Integer) currentValue
+    }
+
+    @Override
+    protected long doReadInt64() {
+        return (Long) currentValue
+    }
+
+    @Override
+    protected Decimal128 doReadDecimal128() {
+        return Decimal128.parse(currentValue.toString())
+    }
+
+    @Override
+    protected String doReadJavaScript() {
+        return (String) currentValue
+    }
+
+    @Override
+    protected String doReadJavaScriptWithScope() {
+        return (String) currentValue
+    }
+
+    @Override
+    protected void doReadMaxKey() {
+        // no-op
+    }
+
+    @Override
+    protected void doReadMinKey() {
+        // no-op
+    }
+
+    @Override
+    protected void doReadNull() {
+        // no-op
+    }
+
+    @Override
+    protected ObjectId doReadObjectId() {
+        return (ObjectId) currentValue
+    }
+
+    @Override
+    protected BsonRegularExpression doReadRegularExpression() {
+        return (BsonRegularExpression) currentValue
+    }
+
+    @Override
+    protected BsonDbPointer doReadDBPointer() {
+        return (BsonDbPointer) currentValue
+    }
+
+    @Override
+    protected void doReadStartArray() {
+        setContext(new Context(getContext(), BsonContextType.ARRAY))
+    }
+
+    @Override
+    protected void doReadStartDocument() {
+        setContext(new Context(getContext(), BsonContextType.DOCUMENT))
+    }
+
+    @Override
+    protected String doReadString() {
+        return (String) currentValue
+    }
+
+    @Override
+    protected String doReadSymbol() {
+        return (String) currentValue
+    }
+
+    @Override
+    protected BsonTimestamp doReadTimestamp() {
+        return (BsonTimestamp) currentValue
+    }
+
+    @Override
+    protected void doReadUndefined() {
+        // no-op
+    }
+
+    @Override
+    protected void doSkipName() {
+        // no-op
+    }
+
+    @Override
+    protected void doSkipValue() {
+        switch (getCurrentBsonType()) {
+            case BsonType.ARRAY:
+                readStartArray()
+                while (readBsonType() != BsonType.END_OF_DOCUMENT) {
+                    skipValue()
+                }
+                readEndArray()
+                break
+            case BsonType.BINARY:
+                readBinaryData()
+                break
+            case BsonType.BOOLEAN:
+                readBoolean()
+                break
+            case BsonType.DATE_TIME:
+                readDateTime()
+                break
+            case BsonType.DOCUMENT:
+                readStartDocument()
+                while (readBsonType() != BsonType.END_OF_DOCUMENT) {
+                    skipName()
+                    skipValue()
+                }
+                readEndDocument()
+                break
+            case BsonType.DOUBLE:
+                readDouble()
+                break
+            case BsonType.INT32:
+                readInt32()
+                break
+            case BsonType.INT64:
+                readInt64()
+                break
+            case BsonType.DECIMAL128:
+                readDecimal128()
+                break
+            case BsonType.JAVASCRIPT:
+                readJavaScript()
+                break
+            case BsonType.JAVASCRIPT_WITH_SCOPE:
+                readJavaScriptWithScope()
+                readStartDocument()
+                while (readBsonType() != BsonType.END_OF_DOCUMENT) {
+                    skipName()
+                    skipValue()
+                }
+                readEndDocument()
+                break
+            case BsonType.MAX_KEY:
+                readMaxKey()
+                break
+            case BsonType.MIN_KEY:
+                readMinKey()
+                break
+            case BsonType.NULL:
+                readNull()
+                break
+            case BsonType.OBJECT_ID:
+                readObjectId()
+                break
+            case BsonType.REGULAR_EXPRESSION:
+                readRegularExpression()
+                break
+            case BsonType.STRING:
+                readString()
+                break
+            case BsonType.SYMBOL:
+                readSymbol()
+                break
+            case BsonType.TIMESTAMP:
+                readTimestamp()
+                break
+            case BsonType.UNDEFINED:
+                readUndefined()
+                break
+            default:
+                break
+        }
+    }
+
+    private JsonToken popToken() {
+        if (pushedToken != null) {
+            JsonToken token = pushedToken
+            pushedToken = null
+            return token
+        }
+        try {
+            return scanner.nextToken()
+        } catch (IOException e) {
+            throw new JsonParseException('Cannot parse JSON due to IO error: ' + e.getMessage(), e)
+        }
+    }
+
+    private void pushToken(final JsonToken token) {
+        if (pushedToken == null) {
+            pushedToken = token
+        } else {
+            throw new BsonInvalidOperationException('There is already a pending token.')
+        }
+    }
+
+    private void verifyToken(final Object expected) {
+        if (expected == null) {
+            throw new IllegalArgumentException("Can't be null")
+        }
+        JsonToken token = popToken()
+        if (!expected.equals(token.getValue())) {
+            throw new JsonParseException("JSON reader expected '%s' but found '%s'.", expected, token.getValue())
+        }
+    }
+
+    private void visitExtendedJSON() {
+        JsonToken nameToken = popToken()
+
+        pushToken(nameToken)
+        setCurrentBsonType(BsonType.DOCUMENT)
+    }
+
+    @Override
+    BsonReaderMark getMark() {
+        return new Mark()
+    }
+
+    @Override
+    protected Context getContext() {
+        return (Context) super.getContext()
+    }
+
+    // Lets the inner Mark restore the context: setContext is protected on AbstractBsonReader, and a
+    // nested class is a different class to the JVM, so it cannot invoke it on the enclosing instance.
+    @PackageScope
+    void resetContext(final AbstractBsonReader.Context parentContext, final BsonContextType contextType) {
+        setContext(new Context(parentContext, contextType))
+    }
+
+    protected class Mark extends AbstractBsonReader.Mark {
+
+        private JsonToken pushedToken
+        private Object currentValue
+        private int position
+
+        protected Mark() {
+            // The superclass is an inner class of AbstractBsonReader: its constructor takes the
+            // enclosing reader as a hidden first argument, which javac supplies implicitly but
+            // Groovy requires to be passed explicitly.
+            super(JsonReader.this)
+            pushedToken = JsonReader.this.pushedToken
+            currentValue = JsonReader.this.currentValue
+            position = JsonReader.this.scanner.position
+        }
+
+        @Override
+        void reset() {
+            super.reset()
+            JsonReader.this.pushedToken = pushedToken
+            JsonReader.this.currentValue = currentValue
+            try {
+                JsonReader.this.scanner.reader.reset()
+            } catch (IOException e) {
+                throw new JsonParseException('Failed to reset reader: ' + e.getMessage(), e)
+            }
+            JsonReader.this.resetContext(getParentContext(), getContextType())
+        }
+
+    }
+
+    protected class Context extends AbstractBsonReader.Context {
+
+        protected Context(final AbstractBsonReader.Context parentContext, final BsonContextType contextType) {
+            // See Mark(): the enclosing reader is the inner superclass's hidden first argument.
+            super(JsonReader.this, parentContext, contextType)
+        }
+
+        @Override
+        protected Context getParentContext() {
+            return (Context) super.getParentContext()
+        }
+
+        // Not redundant: the inherited method is protected on org.bson's Context, so JsonReader
+        // can only call it through this re-declaration in its own nested class.
+        @Override
+        @SuppressWarnings('UnnecessaryOverridingMethod')
+        protected BsonContextType getContextType() {
+            return super.getContextType()
+        }
+
+    }
+
+}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.java b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.java
deleted file mode 100644
index b153385097..0000000000
--- a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonReader.java
+++ /dev/null
@@ -1,509 +0,0 @@
-/*
- * Copyright (c) 2008-2014 MongoDB, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.grails.datastore.bson.json;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
-
-import org.bson.AbstractBsonReader;
-import org.bson.BsonBinary;
-import org.bson.BsonContextType;
-import org.bson.BsonDbPointer;
-import org.bson.BsonInvalidOperationException;
-import org.bson.BsonReaderMark;
-import org.bson.BsonRegularExpression;
-import org.bson.BsonTimestamp;
-import org.bson.BsonType;
-import org.bson.json.JsonParseException;
-import org.bson.types.Decimal128;
-import org.bson.types.ObjectId;
-
-/**
- * A simplified fork of {@link org.bson.json.JsonReader} that works with readers and removes processing related to MongoDB
- *
- * @author Graeme Rocher
- * @since 6.0
- */
-public class JsonReader extends AbstractBsonReader {
-
-    private final JsonScanner scanner;
-    private JsonToken pushedToken;
-    private Object currentValue;
-    private Mark mark;
-
-    /**
-     * Constructs a new instance with the given JSON string.
-     *
-     * @param json     A string representation of a JSON.
-     */
-    public JsonReader(final String json) {
-        this(new StringReader(json));
-    }
-
-    /**
-     * Constructs a new instance with the given JSON reader.
-     *
-     * @param reader The reader.
-     */
-    public JsonReader(final Reader reader) {
-        super();
-        scanner = new JsonScanner(reader);
-        setContext(new Context(null, BsonContextType.TOP_LEVEL));
-    }
-
-    @Override
-    protected BsonBinary doReadBinaryData() {
-        return (BsonBinary) currentValue;
-    }
-
-    @Override
-    protected byte doPeekBinarySubType() {
-        return doReadBinaryData().getType();
-    }
-
-    @Override
-    protected int doPeekBinarySize() {
-        return doReadBinaryData().getData().length;
-    }
-
-    @Override
-    protected boolean doReadBoolean() {
-        return (Boolean) currentValue;
-    }
-
-    //CHECKSTYLE:OFF
-    @Override
-    public BsonType readBsonType() {
-        if (isClosed()) {
-            throw new IllegalStateException("This instance has been closed");
-        }
-        if (getState() == State.INITIAL || getState() == State.DONE || getState() == State.SCOPE_DOCUMENT) {
-            // in JSON the top level value can be of any type so fall through
-            setState(State.TYPE);
-        }
-        if (getState() != State.TYPE) {
-            throwInvalidState("readBSONType", State.TYPE);
-        }
-
-        if (getContext().getContextType() == BsonContextType.DOCUMENT) {
-            JsonToken nameToken = popToken();
-            switch (nameToken.getType()) {
-                case STRING:
-                case UNQUOTED_STRING:
-                    setCurrentName(nameToken.getValue(String.class));
-                    break;
-                case END_OBJECT:
-                    setState(State.END_OF_DOCUMENT);
-                    return BsonType.END_OF_DOCUMENT;
-                default:
-                    throw new JsonParseException("JSON reader was expecting a name but found '%s'.", nameToken.getValue());
-            }
-
-            JsonToken colonToken = popToken();
-            if (colonToken.getType() != JsonTokenType.COLON) {
-                throw new JsonParseException("JSON reader was expecting ':' but found '%s'.", colonToken.getValue());
-            }
-        }
-
-        JsonToken token = popToken();
-        if (getContext().getContextType() == BsonContextType.ARRAY && token.getType() == JsonTokenType.END_ARRAY) {
-            setState(State.END_OF_ARRAY);
-            return BsonType.END_OF_DOCUMENT;
-        }
-
-        boolean noValueFound = false;
-        switch (token.getType()) {
-            case BEGIN_ARRAY:
-                setCurrentBsonType(BsonType.ARRAY);
-                break;
-            case BEGIN_OBJECT:
-                visitExtendedJSON();
-                break;
-            case DOUBLE:
-                setCurrentBsonType(BsonType.DOUBLE);
-                currentValue = token.getValue();
-                break;
-            case END_OF_FILE:
-                setCurrentBsonType(BsonType.END_OF_DOCUMENT);
-                break;
-            case INT32:
-                setCurrentBsonType(BsonType.INT32);
-                currentValue = token.getValue();
-                break;
-            case INT64:
-                setCurrentBsonType(BsonType.INT64);
-                currentValue = token.getValue();
-                break;
-            case REGULAR_EXPRESSION:
-                setCurrentBsonType(BsonType.REGULAR_EXPRESSION);
-                currentValue = token.getValue();
-                break;
-            case STRING:
-                setCurrentBsonType(BsonType.STRING);
-                currentValue = token.getValue();
-                break;
-            case UNQUOTED_STRING:
-                String value = token.getValue(String.class);
-
-                if (JsonToken.BOOLEAN_FALSE.equals(value) || JsonToken.BOOLEAN_TRUE.equals(value)) {
-                    setCurrentBsonType(BsonType.BOOLEAN);
-                    currentValue = Boolean.parseBoolean(value);
-                } else if ("Infinity".equals(value)) {
-                    setCurrentBsonType(BsonType.DOUBLE);
-                    currentValue = Double.POSITIVE_INFINITY;
-                } else if ("NaN".equals(value)) {
-                    setCurrentBsonType(BsonType.DOUBLE);
-                    currentValue = Double.NaN;
-                } else if (JsonToken.NULL.equals(value)) {
-                    setCurrentBsonType(BsonType.NULL);
-                } else if ("undefined".equals(value)) {
-                    setCurrentBsonType(BsonType.UNDEFINED);
-                } else {
-                    noValueFound = true;
-                }
-                break;
-            default:
-                noValueFound = true;
-                break;
-        }
-        if (noValueFound) {
-            throw new JsonParseException("JSON reader was expecting a value but found '%s'.", token.getValue());
-        }
-
-        if (getContext().getContextType() == BsonContextType.ARRAY || getContext().getContextType() == BsonContextType.DOCUMENT) {
-            JsonToken commaToken = popToken();
-            if (commaToken.getType() != JsonTokenType.COMMA) {
-                pushToken(commaToken);
-            }
-        }
-
-        switch (getContext().getContextType()) {
-            case DOCUMENT:
-            case SCOPE_DOCUMENT:
-            default:
-                setState(State.NAME);
-                break;
-            case ARRAY:
-            case JAVASCRIPT_WITH_SCOPE:
-            case TOP_LEVEL:
-                setState(State.VALUE);
-                break;
-        }
-        return getCurrentBsonType();
-    }
-    //CHECKSTYLE:ON
-
-    @Override
-    protected long doReadDateTime() {
-        return (Long) currentValue;
-    }
-
-    @Override
-    protected double doReadDouble() {
-        return (Double) currentValue;
-    }
-
-    @Override
-    protected void doReadEndArray() {
-        setContext(getContext().getParentContext());
-
-        if (getContext().getContextType() == BsonContextType.ARRAY || getContext().getContextType() == BsonContextType.DOCUMENT) {
-            JsonToken commaToken = popToken();
-            if (commaToken.getType() != JsonTokenType.COMMA) {
-                pushToken(commaToken);
-            }
-        }
-    }
-
-    @Override
-    protected void doReadEndDocument() {
-        setContext(getContext().getParentContext());
-        if (getContext() != null && getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) {
-            setContext(getContext().getParentContext()); // JavaScriptWithScope
-            verifyToken(JsonToken.CLOSE_BRACE); // outermost closing bracket for JavaScriptWithScope
-        }
-
-        if (getContext() == null) {
-            throw new JsonParseException("Unexpected end of document.");
-        }
-
-        if (getContext().getContextType() == BsonContextType.ARRAY || getContext().getContextType() == BsonContextType.DOCUMENT) {
-            JsonToken commaToken = popToken();
-            if (commaToken.getType() != JsonTokenType.COMMA) {
-                pushToken(commaToken);
-            }
-        }
-    }
-
-    @Override
-    protected int doReadInt32() {
-        return (Integer) currentValue;
-    }
-
-    @Override
-    protected long doReadInt64() {
-        return (Long) currentValue;
-    }
-
-    @Override
-    protected Decimal128 doReadDecimal128() {
-        return Decimal128.parse(currentValue.toString());
-    }
-
-    @Override
-    protected String doReadJavaScript() {
-        return (String) currentValue;
-    }
-
-    @Override
-    protected String doReadJavaScriptWithScope() {
-        return (String) currentValue;
-    }
-
-    @Override
-    protected void doReadMaxKey() {
-    }
-
-    @Override
-    protected void doReadMinKey() {
-    }
-
-    @Override
-    protected void doReadNull() {
-    }
-
-    @Override
-    protected ObjectId doReadObjectId() {
-        return (ObjectId) currentValue;
-    }
-
-    @Override
-    protected BsonRegularExpression doReadRegularExpression() {
-        return (BsonRegularExpression) currentValue;
-    }
-
-    @Override
-    protected BsonDbPointer doReadDBPointer() {
-        return (BsonDbPointer) currentValue;
-    }
-
-    @Override
-    protected void doReadStartArray() {
-        setContext(new Context(getContext(), BsonContextType.ARRAY));
-    }
-
-    @Override
-    protected void doReadStartDocument() {
-        setContext(new Context(getContext(), BsonContextType.DOCUMENT));
-    }
-
-    @Override
-    protected String doReadString() {
-        return (String) currentValue;
-    }
-
-    @Override
-    protected String doReadSymbol() {
-        return (String) currentValue;
-    }
-
-    @Override
-    protected BsonTimestamp doReadTimestamp() {
-        return (BsonTimestamp) currentValue;
-    }
-
-    @Override
-    protected void doReadUndefined() {
-    }
-
-    @Override
-    protected void doSkipName() {
-    }
-
-    @Override
-    protected void doSkipValue() {
-        switch (getCurrentBsonType()) {
-            case ARRAY:
-                readStartArray();
-                while (readBsonType() != BsonType.END_OF_DOCUMENT) {
-                    skipValue();
-                }
-                readEndArray();
-                break;
-            case BINARY:
-                readBinaryData();
-                break;
-            case BOOLEAN:
-                readBoolean();
-                break;
-            case DATE_TIME:
-                readDateTime();
-                break;
-            case DOCUMENT:
-                readStartDocument();
-                while (readBsonType() != BsonType.END_OF_DOCUMENT) {
-                    skipName();
-                    skipValue();
-                }
-                readEndDocument();
-                break;
-            case DOUBLE:
-                readDouble();
-                break;
-            case INT32:
-                readInt32();
-                break;
-            case INT64:
-                readInt64();
-                break;
-            case DECIMAL128:
-                readDecimal128();
-                break;
-            case JAVASCRIPT:
-                readJavaScript();
-                break;
-            case JAVASCRIPT_WITH_SCOPE:
-                readJavaScriptWithScope();
-                readStartDocument();
-                while (readBsonType() != BsonType.END_OF_DOCUMENT) {
-                    skipName();
-                    skipValue();
-                }
-                readEndDocument();
-                break;
-            case MAX_KEY:
-                readMaxKey();
-                break;
-            case MIN_KEY:
-                readMinKey();
-                break;
-            case NULL:
-                readNull();
-                break;
-            case OBJECT_ID:
-                readObjectId();
-                break;
-            case REGULAR_EXPRESSION:
-                readRegularExpression();
-                break;
-            case STRING:
-                readString();
-                break;
-            case SYMBOL:
-                readSymbol();
-                break;
-            case TIMESTAMP:
-                readTimestamp();
-                break;
-            case UNDEFINED:
-                readUndefined();
-                break;
-            default:
-        }
-    }
-
-    private JsonToken popToken() {
-        if (pushedToken != null) {
-            JsonToken token = pushedToken;
-            pushedToken = null;
-            return token;
-        } else {
-            try {
-                return scanner.nextToken();
-            } catch (IOException e) {
-                throw new JsonParseException("Cannot parse JSON due to IO error: " + e.getMessage(), e);
-            }
-        }
-    }
-
-    private void pushToken(final JsonToken token) {
-        if (pushedToken == null) {
-            pushedToken = token;
-        } else {
-            throw new BsonInvalidOperationException("There is already a pending token.");
-        }
-    }
-
-    private void verifyToken(final Object expected) {
-        if (expected == null) {
-            throw new IllegalArgumentException("Can't be null");
-        }
-        JsonToken token = popToken();
-        if (!expected.equals(token.getValue())) {
-            throw new JsonParseException("JSON reader expected '%s' but found '%s'.", expected, token.getValue());
-        }
-    }
-
-    private void visitExtendedJSON() {
-        JsonToken nameToken = popToken();
-
-        pushToken(nameToken);
-        setCurrentBsonType(BsonType.DOCUMENT);
-    }
-
-    @Override
-    public BsonReaderMark getMark() {
-        return new Mark();
-    }
-
-    @Override
-    protected Context getContext() {
-        return (Context) super.getContext();
-    }
-
-    protected class Mark extends AbstractBsonReader.Mark {
-        private JsonToken pushedToken;
-        private Object currentValue;
-        private int position;
-
-        protected Mark() {
-            super();
-            pushedToken = JsonReader.this.pushedToken;
-            currentValue = JsonReader.this.currentValue;
-            position = JsonReader.this.scanner.position;
-        }
-
-        @Override
-        public void reset() {
-
-            super.reset();
-            JsonReader.this.pushedToken = pushedToken;
-            JsonReader.this.currentValue = currentValue;
-            try {
-                JsonReader.this.scanner.reader.reset();
-            } catch (IOException e) {
-                throw new JsonParseException("Failed to reset reader: " + e.getMessage(), e);
-            }
-            JsonReader.this.setContext(new Context(getParentContext(), getContextType()));
-            mark = null;
-        }
-    }
-
-    protected class Context extends AbstractBsonReader.Context {
-        protected Context(final AbstractBsonReader.Context parentContext, final BsonContextType contextType) {
-            super(parentContext, contextType);
-        }
-
-        protected Context getParentContext() {
-            return (Context) super.getParentContext();
-        }
-
-        protected BsonContextType getContextType() {
-            return super.getContextType();
-        }
-    }
-}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.groovy b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.groovy
new file mode 100644
index 0000000000..3ea35660fb
--- /dev/null
+++ b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.groovy
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2008-2014 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.grails.datastore.bson.json
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.bson.BsonRegularExpression
+import org.bson.json.JsonParseException
+
+/**
+ * Parses the string representation of a JSON object into a set of {@link JsonToken}-derived objects.
+ *
+ * <p>The scanner works on {@code int} character codes (so that {@code -1} can mark end of input) and
+ * compares them against {@code char} constants with {@code ==}. A Groovy {@code switch} cannot be used
+ * for those comparisons: it matches case values with {@code equals()}, and a {@code Character} never
+ * equals an {@code Integer}, so the character switches of the original are written as {@code if} chains.</p>
+ *
+ * @since 3.0
+ */
+@CompileStatic
+@PackageScope
+class JsonScanner {
+
+    private static final char[] NFINITY = ['n', 'f', 'i', 'n', 'i', 't', 'y'] as char[]
+    private static final char DOLLAR = '$'
+    private static final char UNDERSCORE = '_'
+    private static final char SINGLE_QUOTE = '\''
+    private static final char DOT = '.'
+    private static final char PLUS = '+'
+    private static final char ZERO = '0'
+    private static final char UPPER_I = 'I'
+    private static final char LOWER_E = 'e'
+    private static final char UPPER_E = 'E'
+    private static final int END_OF_INPUT = -1
+
+    @PackageScope final PushbackReader reader
+    @PackageScope int position
+
+    /**
+     * Constructs a a new {@code JSONScanner} that produces values scanned from specified {@code JSONBuffer}.
+     *
+     * @param reader A reader to be scanned.
+     */
+    JsonScanner(final Reader reader) {
+        this.reader = new PushbackReader(reader)
+    }
+
+    /**
+     * Finds and returns the next complete token from this scanner. If scanner reached the end of the source, it will return a token with
+     * {@code JSONTokenType.END_OF_FILE} type.
+     *
+     * @return The next token.
+     * @throws JsonParseException if source is invalid.
+     */
+    JsonToken nextToken() throws IOException {
+        int c = readCharacter()
+        while (c != END_OF_INPUT && Character.isWhitespace(c)) {
+            c = readCharacter()
+        }
+        if (c == END_OF_INPUT) {
+            return new JsonToken(JsonTokenType.END_OF_FILE, '<eof>')
+        }
+
+        if (c == JsonToken.OPEN_BRACE) {
+            return new JsonToken(JsonTokenType.BEGIN_OBJECT, JsonToken.OPEN_BRACE)
+        }
+        if (c == JsonToken.CLOSE_BRACE) {
+            return new JsonToken(JsonTokenType.END_OBJECT, JsonToken.CLOSE_BRACE)
+        }
+        if (c == JsonToken.OPEN_BRACKET) {
+            return new JsonToken(JsonTokenType.BEGIN_ARRAY, JsonToken.OPEN_BRACKET)
+        }
+        if (c == JsonToken.CLOSE_BRACKET) {
+            return new JsonToken(JsonTokenType.END_ARRAY, JsonToken.CLOSE_BRACKET)
+        }
+        if (c == JsonToken.OPEN_PARENS) {
+            return new JsonToken(JsonTokenType.LEFT_PAREN, JsonToken.OPEN_PARENS)
+        }
+        if (c == JsonToken.CLOSE_PARENS) {
+            return new JsonToken(JsonTokenType.RIGHT_PAREN, JsonToken.CLOSE_PARENS)
+        }
+        if (c == JsonToken.COLON) {
+            return new JsonToken(JsonTokenType.COLON, JsonToken.CLOSE_BRACKET)
+        }
+        if (c == JsonToken.COMMA) {
+            return new JsonToken(JsonTokenType.COMMA, JsonToken.COMMA)
+        }
+        if (c == JsonToken.BACK_SLASH || c == JsonToken.QUOTE) {
+            return scanString((char) c)
+        }
+        if (c == JsonToken.FORWARD_SLASH) {
+            return scanRegularExpression()
+        }
+        if (c == JsonToken.MINUS || Character.isDigit(c)) {
+            return scanNumber((char) c)
+        }
+        if (c == DOLLAR || c == UNDERSCORE || Character.isLetter(c)) {
+            return scanUnquotedString((char) c)
+        }
+        reader.unread(c)
+        throw new JsonParseException("Invalid JSON input. Position: %d. Character: '%c'.", position, c)
+    }
+
+    protected int readCharacter() throws IOException {
+        position++
+        return reader.read()
+    }
+
+    /**
+     * Reads {@code RegularExpressionToken} from source. The following variants of lexemes are possible:
+     * <pre>
+     *  /pattern/
+     *  /\(pattern\)/
+     *  /pattern/ims
+     * </pre>
+     * Options can include 'i','m','x','s'
+     *
+     * @return The regular expression token.
+     * @throws JsonParseException if regular expression representation is not valid.
+     */
+    private JsonToken scanRegularExpression() throws IOException {
+        RegularExpressionState state = RegularExpressionState.IN_PATTERN
+        StringBuilder optionsBuilder = new StringBuilder()
+        StringBuilder regexBuilder = new StringBuilder()
+        while (true) {
+            int c = readCharacter()
+            switch (state) {
+                case RegularExpressionState.IN_PATTERN:
+                    if (c == JsonToken.FORWARD_SLASH) {
+                        state = RegularExpressionState.IN_OPTIONS
+                    } else if (c == JsonToken.BACK_SLASH) {
+                        state = RegularExpressionState.IN_ESCAPE_SEQUENCE
+                        regexBuilder.append((char) c)
+                    } else {
+                        state = RegularExpressionState.IN_PATTERN
+                        regexBuilder.append((char) c)
+                    }
+                    break
+                case RegularExpressionState.IN_ESCAPE_SEQUENCE:
+                    state = RegularExpressionState.IN_PATTERN
+                    regexBuilder.append((char) c)
+                    break
+                case RegularExpressionState.IN_OPTIONS:
+                    if (isRegularExpressionOption(c)) {
+                        state = RegularExpressionState.IN_OPTIONS
+                        optionsBuilder.append((char) c)
+                    } else if (isValueTerminator(c)) {
+                        state = RegularExpressionState.DONE
+                    } else if (Character.isWhitespace(c)) {
+                        state = RegularExpressionState.DONE
+                    } else {
+                        state = RegularExpressionState.INVALID
+                    }
+                    break
+                default:
+                    break
+            }
+
+            if (state == RegularExpressionState.DONE) {
+                reader.unread(c)
+                BsonRegularExpression regex = new BsonRegularExpression(regexBuilder.toString(), optionsBuilder.toString())
+                return new JsonToken(JsonTokenType.REGULAR_EXPRESSION, regex)
+            }
+            if (state == RegularExpressionState.INVALID) {
+                throw new JsonParseException('Invalid JSON regular expression. Position: %d.', position)
+            }
+        }
+    }
+
+    /**
+     * Reads {@code StringToken} from source.
+     *
+     * @return The string token.
+     * @param startChar
+     */
+    private JsonToken scanUnquotedString(char startChar) throws IOException {
+        StringBuilder builder = new StringBuilder()
+        builder.append(startChar)
+        int c = readCharacter()
+        while (c == DOLLAR || c == UNDERSCORE || Character.isLetterOrDigit(c)) {
+            builder.append((char) c)
+            c = readCharacter()
+        }
+        reader.unread(c)
+        String lexeme = builder.toString()
+        return new JsonToken(JsonTokenType.UNQUOTED_STRING, lexeme)
+    }
+
+    /**
+     * Reads number token from source. The following variants of lexemes are possible:
+     * <pre>
+     *  12
+     *  123
+     *  -0
+     *  -345
+     *  -0.0
+     *  0e1
+     *  0e-1
+     *  -0e-1
+     *  1e12
+     *  -Infinity
+     * </pre>
+     *
+     * @return The number token.
+     * @throws JsonParseException if number representation is invalid.
+     */
+    private JsonToken scanNumber(final char firstChar) throws IOException {
+        int c = (int) firstChar
+
+        NumberState state
+
+        StringBuilder numberBuilder = new StringBuilder()
+        numberBuilder.append(firstChar)
+        if (c == JsonToken.MINUS) {
+            state = NumberState.SAW_LEADING_MINUS
+        } else if (c == ZERO) {
+            state = NumberState.SAW_LEADING_ZERO
+        } else {
+            state = NumberState.SAW_INTEGER_DIGITS
+        }
+
+        JsonTokenType type = JsonTokenType.INT64
+
+        while (true) {
+            c = readCharacter()
+
+            switch (state) {
+                case NumberState.SAW_LEADING_MINUS:
+                    if (c == ZERO) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_LEADING_ZERO
+                    } else if (c == UPPER_I) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_MINUS_I
+                    } else {
+                        numberBuilder.append((char) c)
+                        if (Character.isDigit(c)) {
+                            state = NumberState.SAW_INTEGER_DIGITS
+                        } else {
+                            state = NumberState.INVALID
+                        }
+                    }
+                    break
+                case NumberState.SAW_LEADING_ZERO:
+                case NumberState.SAW_INTEGER_DIGITS:
+                    if (c == DOT) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_DECIMAL_POINT
+                    } else if (c == LOWER_E || c == UPPER_E) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_EXPONENT_LETTER
+                    } else if (isValueTerminator(c)) {
+                        state = NumberState.DONE
+                    } else if (Character.isDigit(c)) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_INTEGER_DIGITS
+                    } else if (Character.isWhitespace(c)) {
+                        state = NumberState.DONE
+                    } else {
+                        state = NumberState.INVALID
+                    }
+                    break
+                case NumberState.SAW_DECIMAL_POINT:
+                    type = JsonTokenType.DOUBLE
+                    if (Character.isDigit(c)) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_FRACTION_DIGITS
+                    } else {
+                        state = NumberState.INVALID
+                    }
+                    break
+                case NumberState.SAW_FRACTION_DIGITS:
+                    if (c == LOWER_E || c == UPPER_E) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_EXPONENT_LETTER
+                    } else if (isValueTerminator(c)) {
+                        state = NumberState.DONE
+                    } else if (Character.isDigit(c)) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_FRACTION_DIGITS
+                    } else if (Character.isWhitespace(c)) {
+                        state = NumberState.DONE
+                    } else {
+                        state = NumberState.INVALID
+                    }
+                    break
+                case NumberState.SAW_EXPONENT_LETTER:
+                    type = JsonTokenType.DOUBLE
+                    if (c == PLUS || c == JsonToken.MINUS) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_EXPONENT_SIGN
+                    } else if (Character.isDigit(c)) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_EXPONENT_DIGITS
+                    } else {
+                        state = NumberState.INVALID
+                    }
+                    break
+                case NumberState.SAW_EXPONENT_SIGN:
+                    if (Character.isDigit(c)) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_EXPONENT_DIGITS
+                    } else {
+                        state = NumberState.INVALID
+                    }
+                    break
+                case NumberState.SAW_EXPONENT_DIGITS:
+                    if (isValueTerminator(c)) {
+                        state = NumberState.DONE
+                    } else if (Character.isDigit(c)) {
+                        numberBuilder.append((char) c)
+                        state = NumberState.SAW_EXPONENT_DIGITS
+                    } else if (Character.isWhitespace(c)) {
+                        state = NumberState.DONE
+                    } else {
+                        state = NumberState.INVALID
+                    }
+                    break
+                case NumberState.SAW_MINUS_I:
+                    boolean sawMinusInfinity = true
+                    numberBuilder.append((char) c)
+                    for (int i = 0; i < NFINITY.length; i++) {
+                        if (c != NFINITY[i]) {
+                            sawMinusInfinity = false
+                            break
+                        }
+                        c = readCharacter()
+                        if (i < NFINITY.length - 1) {
+                            numberBuilder.append((char) c)
+                        }
+                    }
+                    if (sawMinusInfinity) {
+                        type = JsonTokenType.DOUBLE
+                        if (isValueTerminator(c)) {
+                            state = NumberState.DONE
+                        } else if (Character.isWhitespace(c)) {
+                            state = NumberState.DONE
+                        } else {
+                            state = NumberState.INVALID
+                        }
+                    } else {
+                        state = NumberState.INVALID
+                    }
+                    break
+                default:
+                    break
+            }
+
+            if (state == NumberState.INVALID) {
+                throw new JsonParseException('Invalid JSON number')
+            }
+            if (state == NumberState.DONE) {
+                reader.unread(c)
+                if (type == JsonTokenType.DOUBLE) {
+                    return new JsonToken(JsonTokenType.DOUBLE, Double.parseDouble(numberBuilder.toString()))
+                }
+                long value = Long.parseLong(numberBuilder.toString())
+                if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
+                    return new JsonToken(JsonTokenType.INT64, value)
+                }
+                return new JsonToken(JsonTokenType.INT32, (int) value)
+            }
+        }
+    }
+
+    /**
+     * Reads {@code StringToken} from source.
+     *
+     * @return The string token.
+     */
+    private JsonToken scanString(final char quoteCharacter) throws IOException {
+        StringBuilder sb = new StringBuilder()
+
+        while (true) {
+            int c = readCharacter()
+            if (c == JsonToken.BACK_SLASH) {
+                c = readCharacter()
+                if (c == SINGLE_QUOTE) {
+                    sb.append(SINGLE_QUOTE)
+                } else if (c == JsonToken.QUOTE) {
+                    sb.append(JsonToken.QUOTE)
+                } else if (c == JsonToken.BACK_SLASH) {
+                    sb.append(JsonToken.BACK_SLASH)
+                } else if (c == JsonToken.FORWARD_SLASH) {
+                    sb.append(JsonToken.FORWARD_SLASH)
+                } else if (c == ('b' as char)) {
+                    sb.append('\b' as char)
+                } else if (c == ('f' as char)) {
+                    sb.append('\f' as char)
+                } else if (c == ('n' as char)) {
+                    sb.append('\n' as char)
+                } else if (c == ('r' as char)) {
+                    sb.append('\r' as char)
+                } else if (c == ('t' as char)) {
+                    sb.append('\t' as char)
+                } else if (c == ('u' as char)) {
+                    int u1 = readCharacter()
+                    int u2 = readCharacter()
+                    int u3 = readCharacter()
+                    int u4 = readCharacter()
+                    if (u4 != END_OF_INPUT) {
+                        String hex = new String([(char) u1, (char) u2, (char) u3, (char) u4] as char[])
+                        sb.append((char) Integer.parseInt(hex, 16))
+                    }
+                } else {
+                    throw new JsonParseException("Invalid escape sequence in JSON string '\\%c'.", c)
+                }
+            } else {
+                if (c == quoteCharacter) {
+                    return new JsonToken(JsonTokenType.STRING, sb.toString())
+                }
+                if (c != END_OF_INPUT) {
+                    sb.append((char) c)
+                }
+            }
+            if (c == END_OF_INPUT) {
+                throw new JsonParseException('End of file in JSON string.')
+            }
+        }
+    }
+
+    /**
+     * Whether the character ends a scalar value: a separator, a closing bracket or the end of input.
+     */
+    private static boolean isValueTerminator(int c) {
+        return c == JsonToken.COMMA ||
+                c == JsonToken.CLOSE_BRACE ||
+                c == JsonToken.CLOSE_BRACKET ||
+                c == JsonToken.CLOSE_PARENS ||
+                c == END_OF_INPUT
+    }
+
+    private static boolean isRegularExpressionOption(int c) {
+        return c == ('i' as char) || c == ('m' as char) || c == ('x' as char) || c == ('s' as char)
+    }
+
+    private enum NumberState {
+        SAW_LEADING_MINUS,
+        SAW_LEADING_ZERO,
+        SAW_INTEGER_DIGITS,
+        SAW_DECIMAL_POINT,
+        SAW_FRACTION_DIGITS,
+        SAW_EXPONENT_LETTER,
+        SAW_EXPONENT_SIGN,
+        SAW_EXPONENT_DIGITS,
+        SAW_MINUS_I,
+        DONE,
+        INVALID
+    }
+
+    private enum RegularExpressionState {
+        IN_PATTERN,
+        IN_ESCAPE_SEQUENCE,
+        IN_OPTIONS,
+        DONE,
+        INVALID
+    }
+
+}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.java b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.java
deleted file mode 100644
index de1fc55ee9..0000000000
--- a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonScanner.java
+++ /dev/null
@@ -1,532 +0,0 @@
-/*
- * Copyright (c) 2008-2014 MongoDB, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.grails.datastore.bson.json;
-
-import java.io.IOException;
-import java.io.PushbackReader;
-import java.io.Reader;
-
-import org.bson.BsonRegularExpression;
-import org.bson.json.JsonParseException;
-
-/**
- * Parses the string representation of a JSON object into a set of {@link JsonToken}-derived objects.
- *
- * @since 3.0
- */
-class JsonScanner {
-
-    private static final char[] NFINITY = new char[]{'n', 'f', 'i', 'n', 'i', 't', 'y'};
-
-    final PushbackReader reader;
-    int position;
-
-    /**
-     * Constructs a a new {@code JSONScanner} that produces values scanned from specified {@code JSONBuffer}.
-     *
-     * @param reader A reader to be scanned.
-     */
-    public JsonScanner(final Reader reader) {
-        this.reader = new PushbackReader(reader);
-    }
-
-    /**
-     * Finds and returns the next complete token from this scanner. If scanner reached the end of the source, it will return a token with
-     * {@code JSONTokenType.END_OF_FILE} type.
-     *
-     * @return The next token.
-     * @throws JsonParseException if source is invalid.
-     */
-    public JsonToken nextToken() throws IOException {
-
-        int c = readCharacter();
-        while (c != -1 && Character.isWhitespace(c)) {
-            c = readCharacter();
-        }
-        if (c == -1) {
-            return new JsonToken(JsonTokenType.END_OF_FILE, "<eof>");
-        }
-
-        switch (c) {
-            case JsonToken.OPEN_BRACE:
-                return new JsonToken(JsonTokenType.BEGIN_OBJECT, JsonToken.OPEN_BRACE);
-            case JsonToken.CLOSE_BRACE:
-                return new JsonToken(JsonTokenType.END_OBJECT, JsonToken.CLOSE_BRACE);
-            case JsonToken.OPEN_BRACKET:
-                return new JsonToken(JsonTokenType.BEGIN_ARRAY, JsonToken.OPEN_BRACKET);
-            case JsonToken.CLOSE_BRACKET:
-                return new JsonToken(JsonTokenType.END_ARRAY, JsonToken.CLOSE_BRACKET);
-            case JsonToken.OPEN_PARENS:
-                return new JsonToken(JsonTokenType.LEFT_PAREN, JsonToken.OPEN_PARENS);
-            case JsonToken.CLOSE_PARENS:
-                return new JsonToken(JsonTokenType.RIGHT_PAREN, JsonToken.CLOSE_PARENS);
-            case JsonToken.COLON:
-                return new JsonToken(JsonTokenType.COLON, JsonToken.CLOSE_BRACKET);
-            case JsonToken.COMMA:
-                return new JsonToken(JsonTokenType.COMMA, JsonToken.COMMA);
-            case JsonToken.BACK_SLASH:
-            case JsonToken.QUOTE:
-                return scanString((char) c);
-            case JsonToken.FORWARD_SLASH:
-                return scanRegularExpression();
-            default:
-                if (c == JsonToken.MINUS || Character.isDigit(c)) {
-                    return scanNumber((char) c);
-                } else if (c == '$' || c == '_' || Character.isLetter(c)) {
-                    return scanUnquotedString((char) c);
-                } else {
-                    reader.unread(c);
-                    throw new JsonParseException("Invalid JSON input. Position: %d. Character: '%c'.", position, c);
-                }
-        }
-    }
-
-    protected int readCharacter() throws IOException {
-        position++;
-        return reader.read();
-    }
-
-    /**
-     * Reads {@code RegularExpressionToken} from source. The following variants of lexemes are possible:
-     * <pre>
-     *  /pattern/
-     *  /\(pattern\)/
-     *  /pattern/ims
-     * </pre>
-     * Options can include 'i','m','x','s'
-     *
-     * @return The regular expression token.
-     * @throws JsonParseException if regular expression representation is not valid.
-     */
-    private JsonToken scanRegularExpression() throws IOException {
-
-        JsonScanner.RegularExpressionState state = JsonScanner.RegularExpressionState.IN_PATTERN;
-        StringBuilder optionsBuilder = new StringBuilder();
-        StringBuilder regexBuilder = new StringBuilder();
-        while (true) {
-            int c = readCharacter();
-            switch (state) {
-                case IN_PATTERN:
-                    switch (c) {
-                        case JsonToken.FORWARD_SLASH:
-                            state = JsonScanner.RegularExpressionState.IN_OPTIONS;
-                            break;
-                        case JsonToken.BACK_SLASH:
-                            state = JsonScanner.RegularExpressionState.IN_ESCAPE_SEQUENCE;
-                            regexBuilder.append((char) c);
-                            break;
-                        default:
-                            state = JsonScanner.RegularExpressionState.IN_PATTERN;
-                            regexBuilder.append((char) c);
-                            break;
-                    }
-                    break;
-                case IN_ESCAPE_SEQUENCE:
-                    state = RegularExpressionState.IN_PATTERN;
-                    regexBuilder.append((char) c);
-                    break;
-                case IN_OPTIONS:
-                    switch (c) {
-                        case 'i':
-                        case 'm':
-                        case 'x':
-                        case 's':
-                            state = JsonScanner.RegularExpressionState.IN_OPTIONS;
-                            optionsBuilder.append((char) c);
-                            break;
-                        case JsonToken.COMMA:
-                        case JsonToken.CLOSE_BRACE:
-                        case JsonToken.CLOSE_BRACKET:
-                        case JsonToken.CLOSE_PARENS:
-                        case -1:
-                            state = JsonScanner.RegularExpressionState.DONE;
-                            break;
-                        default:
-                            if (Character.isWhitespace(c)) {
-                                state = JsonScanner.RegularExpressionState.DONE;
-                            } else {
-                                state = JsonScanner.RegularExpressionState.INVALID;
-                            }
-                            break;
-                    }
-                    break;
-                default:
-            }
-
-            switch (state) {
-                case DONE:
-                    reader.unread(c);
-                    BsonRegularExpression regex
-                            = new BsonRegularExpression(regexBuilder.toString(), optionsBuilder.toString());
-                    return new JsonToken(JsonTokenType.REGULAR_EXPRESSION, regex);
-                case INVALID:
-                    throw new JsonParseException("Invalid JSON regular expression. Position: %d.", position);
-                default:
-            }
-        }
-    }
-
-    /**
-     * Reads {@code StringToken} from source.
-     *
-     * @return The string token.
-     * @param startChar
-     */
-    private JsonToken scanUnquotedString(char startChar) throws IOException {
-        StringBuilder builder = new StringBuilder();
-        builder.append(startChar);
-        int c = readCharacter();
-        while (c == '$' || c == '_' || Character.isLetterOrDigit(c)) {
-            builder.append((char) c);
-            c = readCharacter();
-        }
-        reader.unread(c);
-        String lexeme = builder.toString();
-        return new JsonToken(JsonTokenType.UNQUOTED_STRING, lexeme);
-    }
-
-    /**
-     * Reads number token from source. The following variants of lexemes are possible:
-     * <pre>
-     *  12
-     *  123
-     *  -0
-     *  -345
-     *  -0.0
-     *  0e1
-     *  0e-1
-     *  -0e-1
-     *  1e12
-     *  -Infinity
-     * </pre>
-     *
-     * @return The number token.
-     * @throws JsonParseException if number representation is invalid.
-     */
-    //CHECKSTYLE:OFF
-    private JsonToken scanNumber(final char firstChar) throws IOException {
-
-        int c = firstChar;
-
-        JsonScanner.NumberState state;
-
-        StringBuilder numberBuilder = new StringBuilder();
-        numberBuilder.append(firstChar);
-        switch (c) {
-            case '-':
-                state = JsonScanner.NumberState.SAW_LEADING_MINUS;
-                break;
-            case '0':
-                state = JsonScanner.NumberState.SAW_LEADING_ZERO;
-                break;
-            default:
-                state = JsonScanner.NumberState.SAW_INTEGER_DIGITS;
-                break;
-        }
-
-        JsonTokenType type = JsonTokenType.INT64;
-
-        while (true) {
-            c = readCharacter();
-
-            switch (state) {
-                case SAW_LEADING_MINUS:
-                    switch (c) {
-                        case '0':
-                            numberBuilder.append((char) c);
-                            state = JsonScanner.NumberState.SAW_LEADING_ZERO;
-                            break;
-                        case 'I':
-                            numberBuilder.append((char) c);
-                            state = JsonScanner.NumberState.SAW_MINUS_I;
-                            break;
-                        default:
-                            numberBuilder.append((char) c);
-                            if (Character.isDigit(c)) {
-                                state = JsonScanner.NumberState.SAW_INTEGER_DIGITS;
-                            } else {
-                                state = JsonScanner.NumberState.INVALID;
-                            }
-                            break;
-                    }
-                    break;
-                case SAW_LEADING_ZERO:
-                case SAW_INTEGER_DIGITS:
-                    switch (c) {
-                        case '.':
-                            numberBuilder.append((char) c);
-                            state = JsonScanner.NumberState.SAW_DECIMAL_POINT;
-                            break;
-                        case 'e':
-                        case 'E':
-                            numberBuilder.append((char) c);
-                            state = JsonScanner.NumberState.SAW_EXPONENT_LETTER;
-                            break;
-                        case JsonToken.COMMA:
-                        case JsonToken.CLOSE_BRACE:
-                        case JsonToken.CLOSE_BRACKET:
-                        case JsonToken.CLOSE_PARENS:
-                        case -1:
-                            state = JsonScanner.NumberState.DONE;
-                            break;
-                        default:
-                            if (Character.isDigit(c)) {
-                                numberBuilder.append((char) c);
-                                state = JsonScanner.NumberState.SAW_INTEGER_DIGITS;
-                            } else if (Character.isWhitespace(c)) {
-                                state = JsonScanner.NumberState.DONE;
-                            } else {
-                                state = JsonScanner.NumberState.INVALID;
-                            }
-                            break;
-                    }
-                    break;
-                case SAW_DECIMAL_POINT:
-                    type = JsonTokenType.DOUBLE;
-                    if (Character.isDigit(c)) {
-                        numberBuilder.append((char) c);
-                        state = JsonScanner.NumberState.SAW_FRACTION_DIGITS;
-                    } else {
-                        state = JsonScanner.NumberState.INVALID;
-                    }
-                    break;
-                case SAW_FRACTION_DIGITS:
-                    switch (c) {
-                        case 'e':
-                        case 'E':
-                            numberBuilder.append((char) c);
-                            state = JsonScanner.NumberState.SAW_EXPONENT_LETTER;
-                            break;
-                        case JsonToken.COMMA:
-                        case JsonToken.CLOSE_BRACE:
-                        case JsonToken.CLOSE_BRACKET:
-                        case JsonToken.CLOSE_PARENS:
-                        case -1:
-                            state = JsonScanner.NumberState.DONE;
-                            break;
-                        default:
-                            if (Character.isDigit(c)) {
-                                numberBuilder.append((char) c);
-                                state = JsonScanner.NumberState.SAW_FRACTION_DIGITS;
-                            } else if (Character.isWhitespace(c)) {
-                                state = JsonScanner.NumberState.DONE;
-                            } else {
-                                state = JsonScanner.NumberState.INVALID;
-                            }
-                            break;
-                    }
-                    break;
-                case SAW_EXPONENT_LETTER:
-                    type = JsonTokenType.DOUBLE;
-                    switch (c) {
-                        case '+':
-                        case '-':
-                            numberBuilder.append((char) c);
-                            state = JsonScanner.NumberState.SAW_EXPONENT_SIGN;
-                            break;
-                        default:
-                            if (Character.isDigit(c)) {
-                                numberBuilder.append((char) c);
-                                state = JsonScanner.NumberState.SAW_EXPONENT_DIGITS;
-                            } else {
-                                state = JsonScanner.NumberState.INVALID;
-                            }
-                            break;
-                    }
-                    break;
-                case SAW_EXPONENT_SIGN:
-                    if (Character.isDigit(c)) {
-                        numberBuilder.append((char) c);
-                        state = JsonScanner.NumberState.SAW_EXPONENT_DIGITS;
-                    } else {
-                        state = JsonScanner.NumberState.INVALID;
-                    }
-                    break;
-                case SAW_EXPONENT_DIGITS:
-                    switch (c) {
-                        case JsonToken.COMMA:
-                        case JsonToken.CLOSE_BRACE:
-                        case JsonToken.CLOSE_BRACKET:
-                        case JsonToken.CLOSE_PARENS:
-                        case -1:
-                            state = JsonScanner.NumberState.DONE;
-                            break;
-                        default:
-                            if (Character.isDigit(c)) {
-                                numberBuilder.append((char) c);
-                                state = JsonScanner.NumberState.SAW_EXPONENT_DIGITS;
-                            } else if (Character.isWhitespace(c)) {
-                                state = JsonScanner.NumberState.DONE;
-                            } else {
-                                state = JsonScanner.NumberState.INVALID;
-                            }
-                            break;
-                    }
-                    break;
-                case SAW_MINUS_I:
-                    boolean sawMinusInfinity = true;
-                    numberBuilder.append((char) c);
-                    for (int i = 0; i < NFINITY.length; i++) {
-                        if (c != NFINITY[i]) {
-                            sawMinusInfinity = false;
-                            break;
-                        }
-                        c = readCharacter();
-                        if (i < NFINITY.length - 1) {
-                            numberBuilder.append((char) c);
-                        }
-                    }
-                    if (sawMinusInfinity) {
-                        type = JsonTokenType.DOUBLE;
-                        switch (c) {
-                            case JsonToken.COMMA:
-                            case JsonToken.CLOSE_BRACE:
-                            case JsonToken.CLOSE_BRACKET:
-                            case JsonToken.CLOSE_PARENS:
-                            case -1:
-                                state = JsonScanner.NumberState.DONE;
-                                break;
-                            default:
-                                if (Character.isWhitespace(c)) {
-                                    state = JsonScanner.NumberState.DONE;
-                                } else {
-                                    state = JsonScanner.NumberState.INVALID;
-                                }
-                                break;
-                        }
-                    } else {
-                        state = JsonScanner.NumberState.INVALID;
-                    }
-                    break;
-                default:
-            }
-
-            switch (state) {
-                case INVALID:
-                    throw new JsonParseException("Invalid JSON number");
-                case DONE:
-                    reader.unread(c);
-                    if (type == JsonTokenType.DOUBLE) {
-                        return new JsonToken(JsonTokenType.DOUBLE, Double.parseDouble(numberBuilder.toString()));
-                    } else {
-                        long value = Long.parseLong(numberBuilder.toString());
-                        if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
-                            return new JsonToken(JsonTokenType.INT64, value);
-                        } else {
-                            return new JsonToken(JsonTokenType.INT32, (int) value);
-                        }
-                    }
-                default:
-            }
-        }
-
-    }
-    //CHECKSTYLE:ON
-
-    /**
-     * Reads {@code StringToken} from source.
-     *
-     * @return The string token.
-     */
-    //CHECKSTYLE:OFF
-    private JsonToken scanString(final char quoteCharacter) throws IOException {
-
-        StringBuilder sb = new StringBuilder();
-
-        while (true) {
-            int c = readCharacter();
-            switch (c) {
-                case JsonToken.BACK_SLASH:
-                    c = readCharacter();
-                    switch (c) {
-                        case '\'':
-                            sb.append('\'');
-                            break;
-                        case JsonToken.QUOTE:
-                            sb.append(JsonToken.QUOTE);
-                            break;
-                        case JsonToken.BACK_SLASH:
-                            sb.append(JsonToken.BACK_SLASH);
-                            break;
-                        case JsonToken.FORWARD_SLASH:
-                            sb.append(JsonToken.FORWARD_SLASH);
-                            break;
-                        case 'b':
-                            sb.append('\b');
-                            break;
-                        case 'f':
-                            sb.append('\f');
-                            break;
-                        case 'n':
-                            sb.append('\n');
-                            break;
-                        case 'r':
-                            sb.append('\r');
-                            break;
-                        case 't':
-                            sb.append('\t');
-                            break;
-                        case 'u':
-                            int u1 = readCharacter();
-                            int u2 = readCharacter();
-                            int u3 = readCharacter();
-                            int u4 = readCharacter();
-                            if (u4 != -1) {
-                                String hex = new String(new char[]{(char) u1, (char) u2, (char) u3, (char) u4});
-                                sb.append((char) Integer.parseInt(hex, 16));
-                            }
-                            break;
-                        default:
-                            throw new JsonParseException("Invalid escape sequence in JSON string '\\%c'.", c);
-                    }
-                    break;
-
-                default:
-                    if (c == quoteCharacter) {
-                        return new JsonToken(JsonTokenType.STRING, sb.toString());
-                    }
-                    if (c != -1) {
-                        sb.append((char) c);
-                    }
-            }
-            if (c == -1) {
-                throw new JsonParseException("End of file in JSON string.");
-            }
-        }
-    }
-
-    private enum NumberState {
-        SAW_LEADING_MINUS,
-        SAW_LEADING_ZERO,
-        SAW_INTEGER_DIGITS,
-        SAW_DECIMAL_POINT,
-        SAW_FRACTION_DIGITS,
-        SAW_EXPONENT_LETTER,
-        SAW_EXPONENT_SIGN,
-        SAW_EXPONENT_DIGITS,
-        SAW_MINUS_I,
-        DONE,
-        INVALID
-    }
-
-    private enum RegularExpressionState {
-        IN_PATTERN,
-        IN_ESCAPE_SEQUENCE,
-        IN_OPTIONS,
-        DONE,
-        INVALID
-    }
-}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.groovy b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.groovy
new file mode 100644
index 0000000000..26e98d3256
--- /dev/null
+++ b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2008-2014 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.grails.datastore.bson.json
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+
+/**
+ * A JSON token.
+ */
+@CompileStatic
+@PackageScope
+class JsonToken {
+
+    @PackageScope static final String BOOLEAN_TRUE = 'true'
+    @PackageScope static final String BOOLEAN_FALSE = 'false'
+    @PackageScope static final String NULL = 'null'
+    @PackageScope static final char FORWARD_SLASH = '/'
+    @PackageScope static final char BACK_SLASH = '\\'
+    @PackageScope static final char MINUS = '-'
+    @PackageScope static final char OPEN_BRACKET = '['
+    @PackageScope static final char CLOSE_BRACKET = ']'
+    @PackageScope static final char OPEN_PARENS = '('
+    @PackageScope static final char CLOSE_PARENS = ')'
+    @PackageScope static final char OPEN_BRACE = '{'
+    @PackageScope static final char CLOSE_BRACE = '}'
+    @PackageScope static final char COLON = ':'
+    @PackageScope static final char COMMA = ','
+    @PackageScope static final char SPACE = ' '
+    @PackageScope static final char NEW_LINE = '\n'
+    @PackageScope static final char QUOTE = '"'
+
+    private final Object value
+    private final JsonTokenType type
+
+    JsonToken(final JsonTokenType type, final Object value) {
+        this.value = value
+        this.type = type
+    }
+
+    Object getValue() {
+        return value
+    }
+
+    def <T> T getValue(final Class<T> clazz) {
+        if (Long == clazz) {
+            if (value instanceof Integer) {
+                return clazz.cast(((Integer) value).longValue())
+            } else if (value instanceof String) {
+                return clazz.cast(Long.valueOf((String) value))
+            }
+        }
+
+        try {
+            return clazz.cast(value)
+        } catch (ClassCastException e) {
+            throw new IllegalStateException(e)
+        }
+    }
+
+    JsonTokenType getType() {
+        return type
+    }
+
+}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.java b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.java
deleted file mode 100644
index fcd722251b..0000000000
--- a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonToken.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2008-2014 MongoDB, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.grails.datastore.bson.json;
-
-/**
- * A JSON token.
- */
-class JsonToken {
-
-    static final String BOOLEAN_TRUE = "true";
-    static final String BOOLEAN_FALSE = "false";
-    static final String NULL = "null";
-    static final char FORWARD_SLASH = '/';
-    static final char BACK_SLASH = '\\';
-    static final char MINUS = '-';
-    static final char OPEN_BRACKET = '[';
-    static final char CLOSE_BRACKET = ']';
-    static final char OPEN_PARENS = '(';
-    static final char CLOSE_PARENS = ')';
-    static final char OPEN_BRACE = '{';
-    static final char CLOSE_BRACE = '}';
-    static final char COLON = ':';
-    static final char COMMA = ',';
-    static final char SPACE = ' ';
-    static final char NEW_LINE = '\n';
-    static final char QUOTE = '"';
-
-    private final Object value;
-    private final JsonTokenType type;
-
-    public JsonToken(final JsonTokenType type, final Object value) {
-        this.value = value;
-        this.type = type;
-    }
-
-    public Object getValue() {
-        return value;
-    }
-
-    public <T> T getValue(final Class<T> clazz) {
-        if (Long.class == clazz) {
-            if (value instanceof Integer) {
-                return clazz.cast(((Integer) value).longValue());
-            } else if (value instanceof String) {
-                return clazz.cast(Long.valueOf((String) value));
-            }
-        }
-
-        try {
-            return clazz.cast(value);
-        } catch (ClassCastException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    public JsonTokenType getType() {
-        return type;
-    }
-}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonTokenType.java b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonTokenType.groovy
similarity index 92%
rename from grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonTokenType.java
rename to grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonTokenType.groovy
index b5139f66e9..b8d50315c2 100644
--- a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonTokenType.java
+++ b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonTokenType.groovy
@@ -13,12 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.grails.datastore.bson.json;
+package org.grails.datastore.bson.json
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
 
 /**
  * Fork of {@link org.bson.json.JsonTokenType}
  */
+@CompileStatic
+@PackageScope
 enum JsonTokenType {
+
     /**
      * An invalid token.
      */
@@ -98,4 +104,5 @@ enum JsonTokenType {
      * An end of file token.
      */
     END_OF_FILE
+
 }
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.groovy b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.groovy
new file mode 100644
index 0000000000..bb607ced74
--- /dev/null
+++ b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.groovy
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2008-2014 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.grails.datastore.bson.json
+
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.bson.AbstractBsonWriter
+import org.bson.AbstractBsonWriter.State
+import org.bson.BSONException
+import org.bson.BsonBinary
+import org.bson.BsonContextType
+import org.bson.BsonDbPointer
+import org.bson.BsonRegularExpression
+import org.bson.BsonTimestamp
+import org.bson.json.JsonMode
+import org.bson.json.JsonWriterSettings
+import org.bson.types.Decimal128
+import org.bson.types.ObjectId
+
+/**
+ * Simplified fork of {@link org.bson.json.JsonWriter} that ignores behaviour specific to MongoDB and produces more compat output
+ *
+ * @author Graeme Rocher
+ * @since 6.0
+ */
+@CompileStatic
+class JsonWriter extends AbstractBsonWriter {
+
+    public static final String ISO_8601 = "yyyy-MM-dd'T'HH:mmZ"
+    public static final TimeZone UTC = TimeZone.getTimeZone('UTC')
+
+    protected final Writer writer
+    protected final JsonWriterSettings settings
+
+    JsonWriter(Writer target) {
+        this(target, JsonWriterSettings.builder().build())
+    }
+
+    JsonWriter(Writer target, JsonWriterSettings settings) {
+        super(settings)
+        this.writer = target
+        this.settings = settings
+        setContext(new Context(null, BsonContextType.TOP_LEVEL, ''))
+    }
+
+    @Override
+    protected void doWriteStartDocument() {
+        try {
+            if (getState() == State.VALUE || getState() == State.SCOPE_DOCUMENT) {
+                writeNameHelper(getName())
+            }
+            BsonContextType contextType = (getState() == State.SCOPE_DOCUMENT) ? BsonContextType.SCOPE_DOCUMENT : BsonContextType.DOCUMENT
+            setContext(new Context(getContext(), contextType, settings.getIndentCharacters()))
+
+            writer.write(JsonToken.OPEN_BRACE)
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteEndDocument() {
+        try {
+            writer.write(JsonToken.CLOSE_BRACE)
+            if (getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) {
+                setContext(getContext().getParentContext())
+                writeEndDocument()
+            } else {
+                setContext(getContext().getParentContext())
+            }
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteStartArray() {
+        try {
+            writeNameHelper(getName())
+            writer.write(JsonToken.OPEN_BRACKET)
+            setContext(new Context(getContext(), BsonContextType.ARRAY, settings.getIndentCharacters()))
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteEndArray() {
+        try {
+            writer.write(JsonToken.CLOSE_BRACKET)
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+        setContext(getContext().getParentContext())
+    }
+
+    @Override
+    protected void doWriteBinaryData(BsonBinary value) {
+        try {
+            writeNameHelper(getName())
+            byte[] data = value.getData()
+            writer.write(Base64.getEncoder().encodeToString(data))
+            setState(getNextState())
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteBoolean(boolean value) {
+        try {
+            writeNameHelper(getName())
+            writer.write(value ? JsonToken.BOOLEAN_TRUE : JsonToken.BOOLEAN_FALSE)
+            setState(getNextState())
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteDateTime(long value) {
+        DateFormat df = new SimpleDateFormat(ISO_8601)
+        df.setTimeZone(UTC)
+        try {
+            writeNameHelper(getName())
+            writer.write(JsonToken.QUOTE)
+            Date date = new Date(value)
+            writer.write(df.format(date))
+            writer.write(JsonToken.QUOTE)
+            setState(getNextState())
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteDBPointer(BsonDbPointer value) {
+        // no-op
+    }
+
+    @Override
+    protected void doWriteDouble(double value) {
+        try {
+            writeNameHelper(getName())
+            writer.write(Double.toString(value))
+            setState(getNextState())
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteInt32(int value) {
+        try {
+            writeNameHelper(getName())
+            writer.write(Integer.toString(value))
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteInt64(long value) {
+        try {
+            writeNameHelper(getName())
+            if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
+                writer.write(Long.toString(value))
+            } else {
+                writer.write(JsonToken.QUOTE)
+                writer.write(Long.toString(value))
+                writer.write(JsonToken.QUOTE)
+            }
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteDecimal128(Decimal128 value) {
+        try {
+            writeNameHelper(getName())
+            writer.write(JsonToken.QUOTE)
+            writer.write(value.toString())
+            writer.write(JsonToken.QUOTE)
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteJavaScript(String value) {
+        // no-op
+    }
+
+    @Override
+    protected void doWriteJavaScriptWithScope(String value) {
+        // no-op
+    }
+
+    @Override
+    protected void doWriteMaxKey() {
+        // no-op
+    }
+
+    @Override
+    protected void doWriteMinKey() {
+        // no-op
+    }
+
+    @Override
+    protected void doWriteNull() {
+        try {
+            writeNameHelper(getName())
+            writer.write(JsonToken.NULL)
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteObjectId(ObjectId value) {
+        try {
+            writeNameHelper(getName())
+            writer.write(value.toString())
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteRegularExpression(BsonRegularExpression regularExpression) {
+        try {
+            switch (settings.getOutputMode()) {
+                case JsonMode.STRICT:
+                    writeStartDocument()
+                    writeString('$regex', regularExpression.getPattern())
+                    writeString('$options', regularExpression.getOptions())
+                    writeEndDocument()
+                    break
+                default:
+                    writeNameHelper(getName())
+                    writer.write(JsonToken.FORWARD_SLASH)
+                    String escaped = (regularExpression.getPattern() == '') ? '(?:)' : regularExpression.getPattern()
+                            .replace('/', '\\/')
+                    writer.write(escaped)
+                    writer.write(JsonToken.FORWARD_SLASH)
+                    writer.write(regularExpression.getOptions())
+                    break
+            }
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteString(final String value) {
+        try {
+            writeNameHelper(getName())
+            writeStringHelper(value)
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    protected void doWriteSymbol(String value) {
+        // no-op
+    }
+
+    @Override
+    protected void doWriteTimestamp(BsonTimestamp value) {
+        // no-op
+    }
+
+    @Override
+    protected void doWriteUndefined() {
+        try {
+            writeNameHelper(getName())
+            writer.write('undefined')
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    @Override
+    void flush() {
+        try {
+            writer.flush()
+        } catch (IOException e) {
+            throwBsonException(e)
+        }
+    }
+
+    protected void writeNameHelper(final String name) throws IOException {
+        switch (getContext().getContextType()) {
+            case BsonContextType.ARRAY:
+                // don't write Array element names in JSON
+                if (getContext().hasElements) {
+                    writer.write(JsonToken.COMMA)
+                }
+                break
+            case BsonContextType.DOCUMENT:
+            case BsonContextType.SCOPE_DOCUMENT:
+                if (getContext().hasElements) {
+                    writer.write(JsonToken.COMMA)
+                }
+                if (settings.isIndent()) {
+                    writer.write(settings.getNewLineCharacters())
+                    writer.write(getContext().indentation)
+                }
+                writeStringHelper(name)
+                writer.write(JsonToken.COLON)
+                break
+            case BsonContextType.TOP_LEVEL:
+                break
+            default:
+                throw new BSONException('Invalid contextType.')
+        }
+
+        getContext().hasElements = true
+    }
+
+    private void throwBsonException(IOException e) {
+        throw new BSONException('Cannot write to writer writer: ' + e.getMessage(), e)
+    }
+
+    @Override
+    protected Context getContext() {
+        return (Context) super.getContext()
+    }
+
+    private void writeStringHelper(final String str) throws IOException {
+        writer.write('"')
+        for (final char c : str.toCharArray()) {
+            switch (c) {
+                case ('"' as char):
+                    writer.write('\\"')
+                    break
+                case ('\\' as char):
+                    writer.write('\\\\')
+                    break
+                case ('\b' as char):
+                    writer.write('\\b')
+                    break
+                case ('\f' as char):
+                    writer.write('\\f')
+                    break
+                case ('\n' as char):
+                    writer.write('\\n')
+                    break
+                case ('\r' as char):
+                    writer.write('\\r')
+                    break
+                case ('\t' as char):
+                    writer.write('\\t')
+                    break
+                default:
+                    if (isPrintable(c)) {
+                        writer.write(c)
+                    } else {
+                        int code = (int) c
+                        writer.write('\\u')
+                        writer.write(Integer.toHexString((code & 0xf000) >> 12))
+                        writer.write(Integer.toHexString((code & 0x0f00) >> 8))
+                        writer.write(Integer.toHexString((code & 0x00f0) >> 4))
+                        writer.write(Integer.toHexString(code & 0x000f))
+                    }
+                    break
+            }
+        }
+        writer.write('"')
+    }
+
+    // The Unicode categories written verbatim rather than as a \\u escape. Compared with == rather
+    // than in a switch: Character.getType returns an int while the category constants are bytes,
+    // and a Groovy switch matches case values with equals(), which is false across those types.
+    private static boolean isPrintable(final char c) {
+        int type = Character.getType(c)
+        return type == Character.UPPERCASE_LETTER ||
+                type == Character.LOWERCASE_LETTER ||
+                type == Character.TITLECASE_LETTER ||
+                type == Character.OTHER_LETTER ||
+                type == Character.DECIMAL_DIGIT_NUMBER ||
+                type == Character.LETTER_NUMBER ||
+                type == Character.OTHER_NUMBER ||
+                type == Character.SPACE_SEPARATOR ||
+                type == Character.CONNECTOR_PUNCTUATION ||
+                type == Character.DASH_PUNCTUATION ||
+                type == Character.START_PUNCTUATION ||
+                type == Character.END_PUNCTUATION ||
+                type == Character.INITIAL_QUOTE_PUNCTUATION ||
+                type == Character.FINAL_QUOTE_PUNCTUATION ||
+                type == Character.OTHER_PUNCTUATION ||
+                type == Character.MATH_SYMBOL ||
+                type == Character.CURRENCY_SYMBOL ||
+                type == Character.MODIFIER_SYMBOL ||
+                type == Character.OTHER_SYMBOL
+    }
+
+    /**
+     * The context for the writer, inheriting all the values from {@link org.bson.AbstractBsonWriter.Context}, and additionally providing
+     * settings for the indentation level and whether there are any child elements at this level.
+     */
+    class Context extends AbstractBsonWriter.Context {
+
+        @PackageScope final String indentation
+        @PackageScope boolean hasElements
+
+        /**
+         * Creates a new context.
+         *
+         * @param parentContext the parent context that can be used for going back up to the parent level
+         * @param contextType   the type of this context
+         * @param indentChars   the String to use for indentation at this level.
+         */
+        Context(final Context parentContext, final BsonContextType contextType, final String indentChars) {
+            // The superclass is an inner class of AbstractBsonWriter: its constructor takes the
+            // enclosing writer as a hidden first argument, which javac supplies implicitly but
+            // Groovy requires to be passed explicitly.
+            super(JsonWriter.this, parentContext, contextType)
+            this.indentation = (parentContext == null) ? indentChars : parentContext.indentation + indentChars
+        }
+
+        @Override
+        Context getParentContext() {
+            return (Context) super.getParentContext()
+        }
+
+    }
+
+}
diff --git a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.java b/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.java
deleted file mode 100644
index 7046ea364a..0000000000
--- a/grails-data-mongodb/bson/src/main/groovy/org/grails/datastore/bson/json/JsonWriter.java
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * Copyright (c) 2008-2014 MongoDB, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.grails.datastore.bson.json;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Base64;
-import java.util.Date;
-import java.util.TimeZone;
-
-import org.bson.AbstractBsonWriter;
-import org.bson.BSONException;
-import org.bson.BsonBinary;
-import org.bson.BsonContextType;
-import org.bson.BsonDbPointer;
-import org.bson.BsonRegularExpression;
-import org.bson.BsonTimestamp;
-import org.bson.json.JsonWriterSettings;
-import org.bson.types.Decimal128;
-import org.bson.types.ObjectId;
-
-/**
- * Simplified fork of {@link org.bson.json.JsonWriter} that ignores behaviour specific to MongoDB and produces more compat output
- *
- * @author Graeme Rocher
- * @since 6.0
- */
-public class JsonWriter extends AbstractBsonWriter {
-
-    public static final String ISO_8601 = "yyyy-MM-dd'T'HH:mmZ";
-    public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
-
-    protected final Writer writer;
-    protected final JsonWriterSettings settings;
-
-    public JsonWriter(Writer target) {
-        this(target, JsonWriterSettings.builder().build());
-    }
-
-    public JsonWriter(Writer target, JsonWriterSettings settings) {
-        super(settings);
-        this.writer = target;
-        this.settings = settings;
-        setContext(new Context(null, BsonContextType.TOP_LEVEL, ""));
-    }
-
-    @Override
-    protected void doWriteStartDocument() {
-        try {
-            if (getState() == State.VALUE || getState() == State.SCOPE_DOCUMENT) {
-                writeNameHelper(getName());
-            }
-            BsonContextType contextType = (getState() == State.SCOPE_DOCUMENT) ? BsonContextType.SCOPE_DOCUMENT : BsonContextType.DOCUMENT;
-            setContext(new Context(getContext(), contextType, settings.getIndentCharacters()));
-
-            writer.write(JsonToken.OPEN_BRACE);
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteEndDocument() {
-        try {
-            writer.write(JsonToken.CLOSE_BRACE);
-            if (getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) {
-                setContext(getContext().getParentContext());
-                writeEndDocument();
-            } else {
-                setContext(getContext().getParentContext());
-            }
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteStartArray() {
-        try {
-            writeNameHelper(getName());
-            writer.write(JsonToken.OPEN_BRACKET);
-            setContext(new Context(getContext(), BsonContextType.ARRAY, settings.getIndentCharacters()));
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-
-    }
-
-    @Override
-    protected void doWriteEndArray() {
-        try {
-            writer.write(JsonToken.CLOSE_BRACKET);
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-        setContext(getContext().getParentContext());
-
-    }
-
-    @Override
-    protected void doWriteBinaryData(BsonBinary value) {
-        try {
-            writeNameHelper(getName());
-            byte[] data = value.getData();
-            writer.write(Base64.getEncoder().encodeToString(data));
-            setState(getNextState());
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteBoolean(boolean value) {
-        try {
-            writeNameHelper(getName());
-            writer.write(value ? JsonToken.BOOLEAN_TRUE : JsonToken.BOOLEAN_FALSE);
-            setState(getNextState());
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteDateTime(long value) {
-        DateFormat df = new SimpleDateFormat(ISO_8601);
-        df.setTimeZone(UTC);
-        try {
-            writeNameHelper(getName());
-            writer.write(JsonToken.QUOTE);
-            Date date = new Date(value);
-            writer.write(df.format(date));
-            writer.write(JsonToken.QUOTE);
-            setState(getNextState());
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteDBPointer(BsonDbPointer value) {
-        // no-op
-    }
-
-    @Override
-    protected void doWriteDouble(double value) {
-        try {
-            writeNameHelper(getName());
-            writer.write(Double.toString(value));
-            setState(getNextState());
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-
-    }
-
-    @Override
-    protected void doWriteInt32(int value) {
-        try {
-            writeNameHelper(getName());
-            writer.write(Integer.toString(value));
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteInt64(long value) {
-        try {
-            writeNameHelper(getName());
-            if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
-                writer.write(Long.toString(value));
-            }
-            else {
-                writer.write(JsonToken.QUOTE);
-                writer.write(Long.toString(value));
-                writer.write(JsonToken.QUOTE);
-            }
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteDecimal128(Decimal128 value) {
-        try {
-            writeNameHelper(getName());
-            writer.write(JsonToken.QUOTE);
-            writer.write(value.toString());
-            writer.write(JsonToken.QUOTE);
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteJavaScript(String value) {
-        // no-op
-    }
-
-    @Override
-    protected void doWriteJavaScriptWithScope(String value) {
-        // no-op
-    }
-
-    @Override
-    protected void doWriteMaxKey() {
-        // no-op
-    }
-
-    @Override
-    protected void doWriteMinKey() {
-        // no-op
-    }
-
-    @Override
-    protected void doWriteNull() {
-        try {
-            writeNameHelper(getName());
-            writer.write(JsonToken.NULL);
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-
-    }
-
-    @Override
-    protected void doWriteObjectId(ObjectId value) {
-        try {
-            writeNameHelper(getName());
-            writer.write(value.toString());
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteRegularExpression(BsonRegularExpression regularExpression) {
-        try {
-            switch (settings.getOutputMode()) {
-                case STRICT:
-                    writeStartDocument();
-                    writeString("$regex", regularExpression.getPattern());
-                    writeString("$options", regularExpression.getOptions());
-                    writeEndDocument();
-                    break;
-                default:
-                    writeNameHelper(getName());
-                    writer.write(JsonToken.FORWARD_SLASH);
-                    String escaped = (regularExpression.getPattern().equals("")) ? "(?:)" : regularExpression.getPattern()
-                            .replace("/", "\\/");
-                    writer.write(escaped);
-                    writer.write(JsonToken.FORWARD_SLASH);
-                    writer.write(regularExpression.getOptions());
-                    break;
-            }
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    protected void doWriteString(final String value) {
-        try {
-            writeNameHelper(getName());
-            writeStringHelper(value);
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-
-    }
-
-    @Override
-    protected void doWriteSymbol(String value) {
-        // no-op
-    }
-
-    @Override
-    protected void doWriteTimestamp(BsonTimestamp value) {
-        // no-op
-    }
-
-    @Override
-    protected void doWriteUndefined() {
-        try {
-            writeNameHelper(getName());
-            writer.write("undefined");
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    @Override
-    public void flush() {
-        try {
-            writer.flush();
-        } catch (IOException e) {
-            throwBsonException(e);
-        }
-    }
-
-    protected void writeNameHelper(final String name) throws IOException {
-        switch (getContext().getContextType()) {
-            case ARRAY:
-                // don't write Array element names in JSON
-                if (getContext().hasElements) {
-                    writer.write(JsonToken.COMMA);
-                }
-                break;
-            case DOCUMENT:
-            case SCOPE_DOCUMENT:
-                if (getContext().hasElements) {
-                    writer.write(JsonToken.COMMA);
-                }
-                if (settings.isIndent()) {
-                    writer.write(settings.getNewLineCharacters());
-                    writer.write(getContext().indentation);
-                }
-                writeStringHelper(name);
-                writer.write(JsonToken.COLON);
-                break;
-            case TOP_LEVEL:
-                break;
-            default:
-                throw new BSONException("Invalid contextType.");
-        }
-
-        getContext().hasElements = true;
-    }
-
-    private void throwBsonException(IOException e) {
-        throw new BSONException("Cannot write to writer writer: " + e.getMessage(), e);
-    }
-
-    @Override
-    protected Context getContext() {
-        return (Context) super.getContext();
-    }
-
-    private void writeStringHelper(final String str) throws IOException {
-        writer.write('"');
-        for (final char c : str.toCharArray()) {
-            switch (c) {
-                case '"':
-                    writer.write("\\\"");
-                    break;
-                case '\\':
-                    writer.write("\\\\");
-                    break;
-                case '\b':
-                    writer.write("\\b");
-                    break;
-                case '\f':
-                    writer.write("\\f");
-                    break;
-                case '\n':
-                    writer.write("\\n");
-                    break;
-                case '\r':
-                    writer.write("\\r");
-                    break;
-                case '\t':
-                    writer.write("\\t");
-                    break;
-                default:
-                    switch (Character.getType(c)) {
-                        case Character.UPPERCASE_LETTER:
-                        case Character.LOWERCASE_LETTER:
-                        case Character.TITLECASE_LETTER:
-                        case Character.OTHER_LETTER:
-                        case Character.DECIMAL_DIGIT_NUMBER:
-                        case Character.LETTER_NUMBER:
-                        case Character.OTHER_NUMBER:
-                        case Character.SPACE_SEPARATOR:
-                        case Character.CONNECTOR_PUNCTUATION:
-                        case Character.DASH_PUNCTUATION:
-                        case Character.START_PUNCTUATION:
-                        case Character.END_PUNCTUATION:
-                        case Character.INITIAL_QUOTE_PUNCTUATION:
-                        case Character.FINAL_QUOTE_PUNCTUATION:
-                        case Character.OTHER_PUNCTUATION:
-                        case Character.MATH_SYMBOL:
-                        case Character.CURRENCY_SYMBOL:
-                        case Character.MODIFIER_SYMBOL:
-                        case Character.OTHER_SYMBOL:
-                            writer.write(c);
-                            break;
-                        default:
-                            writer.write("\\u");
-                            writer.write(Integer.toHexString((c & 0xf000) >> 12));
-                            writer.write(Integer.toHexString((c & 0x0f00) >> 8));
-                            writer.write(Integer.toHexString((c & 0x00f0) >> 4));
-                            writer.write(Integer.toHexString(c & 0x000f));
-                            break;
-                    }
-                    break;
-            }
-        }
-        writer.write('"');
-    }
-
-    /**
-     * The context for the writer, inheriting all the values from {@link org.bson.AbstractBsonWriter.Context}, and additionally providing
-     * settings for the indentation level and whether there are any child elements at this level.
-     */
-
-    public class Context extends AbstractBsonWriter.Context {
-        private final String indentation;
-        private boolean hasElements;
-
-        /**
-         * Creates a new context.
-         *
-         * @param parentContext the parent context that can be used for going back up to the parent level
-         * @param contextType   the type of this context
-         * @param indentChars   the String to use for indentation at this level.
-         */
-        public Context(final Context parentContext, final BsonContextType contextType, final String indentChars) {
-            super(parentContext, contextType);
-            this.indentation = (parentContext == null) ? indentChars : parentContext.indentation + indentChars;
-        }
-
-        @Override
-        public Context getParentContext() {
-            return (Context) super.getParentContext();
-        }
-    }
-}


(grails-core) 11/14: Relocate grails-data-hibernate7-dbmigration-core Java sources into src/main/groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 44f4279fa0ee4ea2603248591a7e64e7686d3ede
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 21:56:30 2026 -0500

    Relocate grails-data-hibernate7-dbmigration-core Java sources into src/main/groovy
    
    Pure move of the 33 liquibase.ext.hibernate Java files from src/main/java to
    src/main/groovy (same packages, no content changes) so that the upcoming
    incremental Groovy conversion can rely on joint compilation for the
    Java-to-Groovy cross-references, and so the module never ends up with real
    content in both source directories (which collides in javadocJar). Also
    declares the Groovy library dependency the module's already-applied groovy
    plugin was missing.
    
    Verified: compileGroovy, checkstyleMain and codenarcMain pass; the
    grails-data-hibernate7-dbmigration-core suite (25 tests) and the
    grails-data-hibernate7-dbmigration suite (86 tests) are green.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 grails-data-hibernate7/dbmigration-core/build.gradle                     | 1 +
 .../liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java     | 0
 .../liquibase/ext/hibernate/database/HibernateClassicDatabase.java       | 0
 .../liquibase/ext/hibernate/database/HibernateDatabase.java              | 0
 .../liquibase/ext/hibernate/database/HibernateEjb3Database.java          | 0
 .../liquibase/ext/hibernate/database/HibernateGenericDialect.java        | 0
 .../liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java    | 0
 .../liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java | 0
 .../liquibase/ext/hibernate/database/JpaPersistenceDatabase.java         | 0
 .../liquibase/ext/hibernate/database/NoOpConnectionProvider.java         | 0
 .../ext/hibernate/database/NoOpMultiTenantConnectionProvider.java        | 0
 .../liquibase/ext/hibernate/database/connection/HibernateConnection.java | 0
 .../ext/hibernate/database/connection/HibernateConnectionMetadata.java   | 0
 .../liquibase/ext/hibernate/database/connection/HibernateDriver.java     | 0
 .../ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java        | 0
 .../ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java    | 0
 .../ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java    | 0
 .../ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java      | 0
 .../hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java  | 0
 .../ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java      | 0
 .../ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java      | 0
 .../ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java        | 0
 .../ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java         | 0
 .../ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java     | 0
 .../ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java          | 0
 .../ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java     | 0
 .../ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java         | 0
 .../ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java       | 0
 .../liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java     | 0
 .../ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java          | 0
 .../hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java   | 0
 .../liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java | 0
 .../ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java      | 0
 .../hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java    | 0
 34 files changed, 1 insertion(+)

diff --git a/grails-data-hibernate7/dbmigration-core/build.gradle b/grails-data-hibernate7/dbmigration-core/build.gradle
index 1c8c41b672..d3bd7cf828 100644
--- a/grails-data-hibernate7/dbmigration-core/build.gradle
+++ b/grails-data-hibernate7/dbmigration-core/build.gradle
@@ -45,6 +45,7 @@ configurations {
 dependencies {
     implementation platform(project(':grails-hibernate7-bom'))
 
+    api 'org.apache.groovy:groovy'
     implementation("org.liquibase:liquibase-core")
     implementation("org.hibernate.orm:hibernate-core")
     implementation("org.hibernate.models:hibernate-models")
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateClassicDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateClassicDatabase.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateDatabase.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateEjb3Database.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateEjb3Database.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateGenericDialect.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateGenericDialect.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/NoOpConnectionProvider.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/NoOpConnectionProvider.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateConnection.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateConnection.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnection.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateConnectionMetadata.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateDriver.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/database/connection/HibernateDriver.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/connection/HibernateDriver.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedColumnChangeGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedForeignKeyChangeGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedPrimaryKeyChangeGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedSequenceChangeGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateChangedUniqueConstraintChangeGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateMissingSequenceChangeGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/diff/HibernateUnexpectedIndexChangeGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateCatalogSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateColumnSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateForeignKeySnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateIndexSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernatePrimaryKeySnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSchemaSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSequenceSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateTableSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateUniqueConstraintSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/HibernateViewSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java
similarity index 100%
rename from grails-data-hibernate7/dbmigration-core/src/main/java/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java


(grails-core) 12/14: Convert grails-data-hibernate7-dbmigration-core database package to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit c585c22769ee23c5bae2d1e876d30f3e13323b67
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 22:08:48 2026 -0500

    Convert grails-data-hibernate7-dbmigration-core database package to Groovy
    
    Converts the liquibase.ext.hibernate.database package (HibernateDatabase,
    HibernateClassicDatabase, HibernateEjb3Database, HibernateGenericDialect,
    HibernateSpringBeanDatabase, HibernateSpringPackageDatabase,
    JpaPersistenceDatabase, NoOpConnectionProvider,
    NoOpMultiTenantConnectionProvider) and customfactory.CustomMetadataFactory
    to @CompileStatic Groovy. Headers are left as they were (these files carry
    none).
    
    HibernateDatabase and its subclasses are additionally compiled with @POJO so
    that they do not implement GroovyObject: their public getProperty(String)
    API method would otherwise be treated by the Groovy runtime as the dynamic
    property-access hook and hijack property lookups from dynamic callers such
    as the sibling dbmigration module's Spock specs. The Envers setting keys in
    HibernateSpringPackageDatabase are duplicated as literals because
    hibernate-envers is compileOnly and Groovy does not inline constants from
    other classes. The Optional.of(...).orElseThrow(...) guard in
    JpaPersistenceDatabase, which could only ever throw NullPointerException,
    now throws the IllegalStateException it was written to produce.
    
    Verified with checkstyleMain/codenarcMain and the full
    grails-data-hibernate7-dbmigration-core (25 tests) and
    grails-data-hibernate7-dbmigration (86 tests) suites.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 ...taFactory.java => CustomMetadataFactory.groovy} |  15 +-
 .../database/HibernateClassicDatabase.groovy       |  98 +++++++
 .../database/HibernateClassicDatabase.java         |  92 ------
 ...rnateDatabase.java => HibernateDatabase.groovy} | 319 +++++++++++----------
 .../database/HibernateEjb3Database.groovy          | 175 +++++++++++
 .../hibernate/database/HibernateEjb3Database.java  | 156 ----------
 .../database/HibernateGenericDialect.groovy        |  17 ++
 .../database/HibernateGenericDialect.java          |  13 -
 .../database/HibernateSpringBeanDatabase.groovy    | 205 +++++++++++++
 .../database/HibernateSpringBeanDatabase.java      | 184 ------------
 .../database/HibernateSpringPackageDatabase.groovy | 176 ++++++++++++
 .../database/HibernateSpringPackageDatabase.java   | 165 -----------
 .../database/JpaPersistenceDatabase.groovy         |  65 +++++
 .../hibernate/database/JpaPersistenceDatabase.java |  63 ----
 .../database/NoOpConnectionProvider.groovy         |  74 +++++
 .../hibernate/database/NoOpConnectionProvider.java |  70 -----
 .../NoOpMultiTenantConnectionProvider.groovy       |  66 +++++
 .../NoOpMultiTenantConnectionProvider.java         |  62 ----
 18 files changed, 1049 insertions(+), 966 deletions(-)

diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.groovy
similarity index 63%
rename from grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.groovy
index 91437ab468..706a12f7ce 100644
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.groovy
@@ -1,17 +1,20 @@
-package liquibase.ext.hibernate.customfactory;
+package liquibase.ext.hibernate.customfactory
 
-import liquibase.ext.hibernate.database.HibernateDatabase;
-import liquibase.ext.hibernate.database.connection.HibernateConnection;
-import org.hibernate.boot.Metadata;
+import groovy.transform.CompileStatic
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.ext.hibernate.database.connection.HibernateConnection
+import org.hibernate.boot.Metadata
 
 /**
  * Implement this interface to dynamically generate a hibernate:ejb3 configuration.
  * For example, if you create a class called com.example.hibernate.MyConfig, specify a url of hibernate:ejb3:com.example.hibernate.MyConfig.
  */
-public interface CustomMetadataFactory {
+@CompileStatic
+interface CustomMetadataFactory {
 
     /*
      * Create a hibernate Configuration for the given database and connection.
      */
-    Metadata getMetadata(HibernateDatabase hibernateDatabase, HibernateConnection connection);
+    Metadata getMetadata(HibernateDatabase hibernateDatabase, HibernateConnection connection)
+
 }
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.groovy
new file mode 100644
index 0000000000..35c996b965
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.groovy
@@ -0,0 +1,98 @@
+package liquibase.ext.hibernate.database
+
+import groovy.transform.CompileStatic
+import liquibase.database.DatabaseConnection
+import liquibase.exception.DatabaseException
+import org.hibernate.boot.Metadata
+import org.hibernate.boot.MetadataSources
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder
+import org.hibernate.cfg.AvailableSettings
+import org.hibernate.cfg.Configuration
+import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider
+import org.hibernate.service.ServiceRegistry
+
+/**
+ * Database implementation for "classic" hibernate configurations.
+ */
+@CompileStatic
+class HibernateClassicDatabase extends HibernateDatabase {
+
+    protected Configuration configuration
+    // Track the registry so we can close it later
+    private ServiceRegistry serviceRegistry
+
+    @Override
+    boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
+        String url = conn.getURL()
+        return url != null && url.startsWith('hibernate:classic:')
+    }
+
+    @Override
+    protected String findDialectName() {
+        String dialectName = super.findDialectName()
+        if (dialectName != null) {
+            return dialectName
+        }
+        return configuration != null ? configuration.getProperty(AvailableSettings.DIALECT) : null
+    }
+
+    @Override
+    protected Metadata buildMetadataFromPath() throws DatabaseException {
+        this.configuration = new Configuration()
+        String path = getHibernateConnection().getPath()
+        if (path == null) {
+            throw new IllegalStateException('Hibernate connection path is null')
+        }
+        this.configuration.configure(path)
+
+        return super.buildMetadataFromPath()
+    }
+
+    @Override
+    protected void configureSources(MetadataSources sources) {
+        Configuration config = new Configuration(sources)
+        String path = getHibernateConnection().getPath()
+        if (path == null) {
+            throw new IllegalStateException('Hibernate connection path is null')
+        }
+        config.configure(path)
+
+        config.setProperty(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString())
+        config.setProperty('hibernate.cache.use_second_level_cache', 'false')
+
+        // Assign to the class field instead of a local variable
+        this.serviceRegistry = configuration
+                .getStandardServiceRegistryBuilder()
+                .applySettings(config.getProperties())
+                .addService(ConnectionProvider, new NoOpConnectionProvider())
+                .addService(MultiTenantConnectionProvider, new NoOpMultiTenantConnectionProvider())
+                .build()
+
+        // We build the factory to finalize the configuration, but we don't
+        // need to hold a reference to it here if we aren't using it.
+        config.buildSessionFactory(serviceRegistry)
+    }
+
+    @Override
+    void close() throws DatabaseException {
+        try {
+            if (serviceRegistry != null) {
+                StandardServiceRegistryBuilder.destroy(serviceRegistry)
+            }
+        } finally {
+            super.close()
+        }
+    }
+
+    @Override
+    String getShortName() {
+        return 'hibernateClassic'
+    }
+
+    @Override
+    protected String getDefaultDatabaseProductName() {
+        return 'Hibernate Classic'
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.java
deleted file mode 100644
index 3551909b8f..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateClassicDatabase.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import java.util.Optional;
-
-import liquibase.database.DatabaseConnection;
-import liquibase.exception.DatabaseException;
-import org.hibernate.boot.Metadata;
-import org.hibernate.boot.MetadataSources;
-import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
-import org.hibernate.cfg.AvailableSettings;
-import org.hibernate.cfg.Configuration;
-import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
-import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
-import org.hibernate.service.ServiceRegistry;
-
-/**
- * Database implementation for "classic" hibernate configurations.
- */
-public class HibernateClassicDatabase extends HibernateDatabase {
-
-    protected Configuration configuration;
-    // Track the registry so we can close it later
-    private ServiceRegistry serviceRegistry;
-
-    @Override
-    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
-        return Optional.ofNullable(conn.getURL())
-                .map(url -> url.startsWith("hibernate:classic:"))
-                .orElse(false);
-    }
-
-    @Override
-    protected String findDialectName() {
-        return Optional.ofNullable(super.findDialectName())
-                .or(() -> Optional.ofNullable(configuration).map(c -> c.getProperty(AvailableSettings.DIALECT)))
-                .orElse(null);
-    }
-
-    @Override
-    protected Metadata buildMetadataFromPath() throws DatabaseException {
-        this.configuration = new Configuration();
-        String path = Optional.ofNullable(getHibernateConnection().getPath())
-                .orElseThrow(() -> new IllegalStateException("Hibernate connection path is null"));
-        this.configuration.configure(path);
-
-        return super.buildMetadataFromPath();
-    }
-
-    @Override
-    protected void configureSources(MetadataSources sources) {
-        Configuration config = new Configuration(sources);
-        String path = Optional.ofNullable(getHibernateConnection().getPath())
-                .orElseThrow(() -> new IllegalStateException("Hibernate connection path is null"));
-        config.configure(path);
-
-        config.setProperty(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString());
-        config.setProperty("hibernate.cache.use_second_level_cache", "false");
-
-        // Assign to the class field instead of a local variable
-        this.serviceRegistry = configuration
-                .getStandardServiceRegistryBuilder()
-                .applySettings(config.getProperties())
-                .addService(ConnectionProvider.class, new NoOpConnectionProvider())
-                .addService(MultiTenantConnectionProvider.class, new NoOpMultiTenantConnectionProvider())
-                .build();
-
-        // We build the factory to finalize the configuration, but we don't
-        // need to hold a reference to it here if we aren't using it.
-        config.buildSessionFactory(serviceRegistry);
-    }
-
-    @Override
-    public void close() throws DatabaseException {
-        try {
-            if (serviceRegistry != null) {
-                StandardServiceRegistryBuilder.destroy(serviceRegistry);
-            }
-        } finally {
-            super.close();
-        }
-    }
-
-    @Override
-    public String getShortName() {
-        return "hibernateClassic";
-    }
-
-    @Override
-    protected String getDefaultDatabaseProductName() {
-        return "Hibernate Classic";
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.groovy
similarity index 55%
rename from grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.java
rename to grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.groovy
index b6d2d620b7..3eac493a5b 100644
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.java
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateDatabase.groovy
@@ -1,85 +1,99 @@
-package liquibase.ext.hibernate.database;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.concurrent.atomic.AtomicReference;
-
-import liquibase.Scope;
-import liquibase.database.AbstractJdbcDatabase;
-import liquibase.database.DatabaseConnection;
-import liquibase.database.jvm.JdbcConnection;
-import liquibase.exception.DatabaseException;
-import liquibase.exception.UnexpectedLiquibaseException;
-import liquibase.ext.hibernate.customfactory.CustomMetadataFactory;
-import liquibase.ext.hibernate.database.connection.HibernateConnection;
-import liquibase.ext.hibernate.database.connection.HibernateDriver;
-import org.hibernate.boot.Metadata;
-import org.hibernate.boot.MetadataBuilder;
-import org.hibernate.boot.MetadataSources;
-import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
-import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
-import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
-import org.hibernate.cfg.AvailableSettings;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.dialect.MySQLDialect;
-import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
-import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
-import org.hibernate.service.ServiceRegistry;
+package liquibase.ext.hibernate.database
+
+import java.lang.reflect.InvocationTargetException
+import java.sql.Connection
+import java.util.concurrent.atomic.AtomicReference
+
+import groovy.transform.CompileStatic
+import groovy.transform.stc.POJO
+import liquibase.Scope
+import liquibase.database.AbstractJdbcDatabase
+import liquibase.database.DatabaseConnection
+import liquibase.database.jvm.JdbcConnection
+import liquibase.exception.DatabaseException
+import liquibase.exception.UnexpectedLiquibaseException
+import liquibase.ext.hibernate.customfactory.CustomMetadataFactory
+import liquibase.ext.hibernate.database.connection.HibernateConnection
+import liquibase.ext.hibernate.database.connection.HibernateDriver
+import org.hibernate.boot.Metadata
+import org.hibernate.boot.MetadataBuilder
+import org.hibernate.boot.MetadataSources
+import org.hibernate.boot.model.naming.ImplicitNamingStrategy
+import org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
+import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
+import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
+import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
+import org.hibernate.boot.model.naming.PhysicalNamingStrategy
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder
+import org.hibernate.cfg.AvailableSettings
+import org.hibernate.dialect.Dialect
+import org.hibernate.dialect.MySQLDialect
+import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider
+import org.hibernate.service.ServiceRegistry
 
 /**
  * Base class for all Hibernate Databases. This extension interacts with Hibernate by creating standard liquibase.database.Database implementations that
  * bridge what Liquibase expects and the Hibernate APIs.
+ *
+ * <p>Compiled as a {@link POJO} (no {@code GroovyObject} implementation) because the public
+ * {@link #getProperty(String)} API method would otherwise be treated by Groovy's runtime as the
+ * dynamic property-access hook and hijack {@code database.someProperty} for every dynamic caller.
+ * Subclasses must carry the same annotation.
  */
-public abstract class HibernateDatabase extends AbstractJdbcDatabase {
+@CompileStatic
+@POJO
+abstract class HibernateDatabase extends AbstractJdbcDatabase {
 
-    private Metadata metadata;
-    protected Dialect dialect;
+    private Metadata metadata
+    protected Dialect dialect
 
-    private boolean indexesForForeignKeys = false;
-    public static final String DEFAULT_SCHEMA = "HIBERNATE";
-    public static final String HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS = "hibernate.temp.use_jdbc_metadata_defaults";
+    private boolean indexesForForeignKeys = false
+    public static final String DEFAULT_SCHEMA = 'HIBERNATE'
+    public static final String HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS = 'hibernate.temp.use_jdbc_metadata_defaults'
 
-    public HibernateDatabase() {
-        setDefaultCatalogName(DEFAULT_SCHEMA);
-        setDefaultSchemaName(DEFAULT_SCHEMA);
+    HibernateDatabase() {
+        setDefaultCatalogName(DEFAULT_SCHEMA)
+        setDefaultSchemaName(DEFAULT_SCHEMA)
     }
 
     @Override
-    public boolean requiresPassword() {
-        return false;
+    boolean requiresPassword() {
+        return false
     }
 
     @Override
-    public boolean requiresUsername() {
-        return false;
+    boolean requiresUsername() {
+        return false
     }
 
     @Override
-    public String getDefaultDriver(String url) {
-        if (url.startsWith("hibernate")) {
-            return HibernateDriver.class.getName();
+    String getDefaultDriver(String url) {
+        if (url.startsWith('hibernate')) {
+            return HibernateDriver.getName()
         }
-        return null;
+        return null
     }
 
     @Override
-    public int getPriority() {
-        return PRIORITY_DEFAULT;
+    int getPriority() {
+        return PRIORITY_DEFAULT
     }
 
     @Override
-    public void setConnection(DatabaseConnection conn) {
-        super.setConnection(conn);
+    void setConnection(DatabaseConnection conn) {
+        super.setConnection(conn)
 
         try {
             Scope.getCurrentScope()
                     .getLog(getClass())
-                    .info("Reading hibernate configuration " + getConnection().getURL());
+                    .info('Reading hibernate configuration ' + getConnection().getURL())
 
-            this.metadata = buildMetadata();
+            this.metadata = buildMetadata()
 
-            afterSetup();
+            afterSetup()
         } catch (DatabaseException e) {
-            throw new UnexpectedLiquibaseException(e);
+            throw new UnexpectedLiquibaseException(e)
         }
     }
 
@@ -87,42 +101,41 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
      * Called by {@link #createMetadataSources()} to determine the correct dialect name based on url parameters, configuration files, etc.
      */
     protected String findDialectName() {
-        return getHibernateConnection().getProperties().getProperty(AvailableSettings.DIALECT);
+        return getHibernateConnection().getProperties().getProperty(AvailableSettings.DIALECT)
     }
 
     /**
      * Returns the dialect determined during database initialization.
      */
-    public Dialect getDialect() {
-        return dialect;
+    Dialect getDialect() {
+        return dialect
     }
 
     /**
      * Return the hibernate {@link Metadata} used by this database.
      */
-    public Metadata getMetadata() {
-        return metadata;
+    Metadata getMetadata() {
+        return metadata
     }
 
     /**
      * Convenience method to return the underlying HibernateConnection in the JdbcConnection returned by {@link #getConnection()}
      */
     protected HibernateConnection getHibernateConnection() {
-        DatabaseConnection originalConnection = getConnection();
-        if (originalConnection instanceof liquibase.database.jvm.JdbcConnection) {
-            java.sql.Connection underlyingConnection =
-                    ((JdbcConnection) originalConnection).getUnderlyingConnection();
+        DatabaseConnection originalConnection = getConnection()
+        if (originalConnection instanceof JdbcConnection) {
+            Connection underlyingConnection = ((JdbcConnection) originalConnection).getUnderlyingConnection()
             if (underlyingConnection instanceof HibernateConnection) {
-                return (HibernateConnection) underlyingConnection;
+                return (HibernateConnection) underlyingConnection
             } else {
-                throw new UnexpectedLiquibaseException("Underlying connection is not a HibernateConnection: " +
-                        underlyingConnection.getClass().getName());
+                throw new UnexpectedLiquibaseException('Underlying connection is not a HibernateConnection: ' +
+                        underlyingConnection.getClass().getName())
             }
         } else if (originalConnection instanceof HibernateConnection) {
-            return (HibernateConnection) originalConnection;
+            return (HibernateConnection) originalConnection
         } else {
             throw new UnexpectedLiquibaseException(
-                    "Unknown connection type: " + originalConnection.getClass().getName());
+                    'Unknown connection type: ' + originalConnection.getClass().getName())
         }
     }
 
@@ -132,32 +145,31 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
      * Otherwise, it delegates to {@link #buildMetadataFromPath()}
      */
     protected final Metadata buildMetadata() throws DatabaseException {
-        String path = getHibernateConnection().getPath();
-        if (!path.contains("/")) {
+        String path = getHibernateConnection().getPath()
+        if (!path.contains('/')) {
             try {
                 // Fix: Use Thread Context ClassLoader for J2EE/Spring compliance (PMD #7)
-                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-                Class<?> clazz = contextClassLoader.loadClass(path);
+                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader()
+                Class<?> clazz = contextClassLoader.loadClass(path)
 
-                if (CustomMetadataFactory.class.isAssignableFrom(clazz)) {
+                if (CustomMetadataFactory.isAssignableFrom(clazz)) {
                     try {
-                        return ((CustomMetadataFactory)
-                                clazz.getDeclaredConstructor().newInstance())
-                                .getMetadata(this, getHibernateConnection());
+                        return ((CustomMetadataFactory) clazz.getDeclaredConstructor().newInstance())
+                                .getMetadata(this, getHibernateConnection())
                     } catch (InstantiationException |
                              IllegalAccessException |
                              InvocationTargetException |
                              NoSuchMethodException e) {
-                        throw new DatabaseException(e);
+                        throw new DatabaseException(e)
                     }
                 }
             } catch (ClassNotFoundException ignore) {
                 // Fix: Avoid empty catch blocks by documenting the intent (PMD #6)
-                Scope.getCurrentScope().getLog(getClass()).debug("Path " + path + " is not a CustomMetadataFactory, continuing with standard build.");
+                Scope.getCurrentScope().getLog(getClass()).debug('Path ' + path + ' is not a CustomMetadataFactory, continuing with standard build.')
             }
         }
 
-        return buildMetadataFromPath();
+        return buildMetadataFromPath()
     }
 
     /**
@@ -166,30 +178,30 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
      * but this method can be overridden with any provider-specific implementations needed.
      */
     protected Metadata buildMetadataFromPath() throws DatabaseException {
-        MetadataSources sources = createMetadataSources();
-        configureSources(sources);
+        MetadataSources sources = createMetadataSources()
+        configureSources(sources)
 
-        MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
-        configureMetadataBuilder(metadataBuilder);
+        MetadataBuilder metadataBuilder = sources.getMetadataBuilder()
+        configureMetadataBuilder(metadataBuilder)
 
-        AtomicReference<Throwable> thrownException = new AtomicReference<>();
-        AtomicReference<Metadata> result = new AtomicReference<>();
+        AtomicReference<Throwable> thrownException = new AtomicReference<>()
+        AtomicReference<Metadata> result = new AtomicReference<>()
 
-        Thread t = new Thread(() -> result.set(metadataBuilder.build()));
-        t.setContextClassLoader(Thread.currentThread().getContextClassLoader());
-        t.setUncaughtExceptionHandler((_t, e) -> thrownException.set(e));
-        t.start();
+        Thread t = new Thread({ -> result.set(metadataBuilder.build()) } as Runnable)
+        t.setContextClassLoader(Thread.currentThread().getContextClassLoader())
+        t.setUncaughtExceptionHandler({ Thread thread, Throwable e -> thrownException.set(e) } as Thread.UncaughtExceptionHandler)
+        t.start()
         try {
-            t.join();
+            t.join()
         } catch (InterruptedException e) {
-            Thread.currentThread().interrupt(); // Restore interrupted status
-            throw new DatabaseException(e);
+            Thread.currentThread().interrupt() // Restore interrupted status
+            throw new DatabaseException(e)
         }
-        Throwable thrown = thrownException.get();
+        Throwable thrown = thrownException.get()
         if (thrown != null) {
-            throw new DatabaseException(thrown);
+            throw new DatabaseException(thrown)
         }
-        return result.get();
+        return result.get()
     }
 
     /**
@@ -197,50 +209,50 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
      * Normally, the result of this method is passed through {@link #configureSources(MetadataSources)}.
      */
     protected MetadataSources createMetadataSources() throws DatabaseException {
-        String dialectString = findDialectName();
+        String dialectString = findDialectName()
         if (dialectString != null) {
             try {
                 dialect = (Dialect) Thread.currentThread()
                         .getContextClassLoader()
                         .loadClass(dialectString)
                         .getDeclaredConstructor()
-                        .newInstance();
-                Scope.getCurrentScope().getLog(getClass()).info("Using dialect " + dialectString);
+                        .newInstance()
+                Scope.getCurrentScope().getLog(getClass()).info('Using dialect ' + dialectString)
             } catch (InstantiationException |
                      IllegalAccessException |
                      InvocationTargetException |
                      NoSuchMethodException |
                      ClassNotFoundException e) {
-                throw new DatabaseException(e);
+                throw new DatabaseException(e)
             }
         } else {
             Scope.getCurrentScope()
                     .getLog(getClass())
-                    .info("Could not determine hibernate dialect, using HibernateGenericDialect");
-            dialect = new HibernateGenericDialect();
+                    .info('Could not determine hibernate dialect, using HibernateGenericDialect')
+            dialect = new HibernateGenericDialect()
         }
 
         ServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
                 .applySetting(AvailableSettings.DIALECT, dialect)
                 .applySetting(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString())
-                .addService(ConnectionProvider.class, new NoOpConnectionProvider())
-                .addService(MultiTenantConnectionProvider.class, new NoOpMultiTenantConnectionProvider())
-                .build();
+                .addService(ConnectionProvider, new NoOpConnectionProvider())
+                .addService(MultiTenantConnectionProvider, new NoOpMultiTenantConnectionProvider())
+                .build()
 
-        return new MetadataSources(standardRegistry);
+        return new MetadataSources(standardRegistry)
     }
 
     /**
      * Adds any implementation-specific sources to the given {@link MetadataSources}
      */
-    protected abstract void configureSources(MetadataSources sources) throws DatabaseException;
+    protected abstract void configureSources(MetadataSources sources) throws DatabaseException
 
     protected void configurePhysicalNamingStrategy(String physicalNamingStrategy, MetadataBuilder builder)
             throws DatabaseException {
-        String namingStrategy;
+        String namingStrategy
         namingStrategy = getHibernateConnection()
                 .getProperties()
-                .getProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, physicalNamingStrategy);
+                .getProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY, physicalNamingStrategy)
 
         try {
             if (namingStrategy != null) {
@@ -248,51 +260,47 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
                         .getContextClassLoader()
                         .loadClass(namingStrategy)
                         .getDeclaredConstructor()
-                        .newInstance());
+                        .newInstance())
             }
         } catch (InstantiationException |
                  IllegalAccessException |
                  InvocationTargetException |
                  NoSuchMethodException |
                  ClassNotFoundException e) {
-            throw new DatabaseException(e);
+            throw new DatabaseException(e)
         }
     }
 
     protected void configureImplicitNamingStrategy(String implicitNamingStrategy, MetadataBuilder builder)
             throws DatabaseException {
-        String namingStrategy;
+        String namingStrategy
         namingStrategy = getHibernateConnection()
                 .getProperties()
-                .getProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, implicitNamingStrategy);
+                .getProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, implicitNamingStrategy)
 
         try {
             if (namingStrategy != null) {
                 switch (namingStrategy) {
-                    case "default":
-                    case "jpa":
-                        builder.applyImplicitNamingStrategy(
-                                org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl.INSTANCE);
-                        break;
-                    case "legacy-hbm":
-                        builder.applyImplicitNamingStrategy(
-                                org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl.INSTANCE);
-                        break;
-                    case "legacy-jpa":
-                        builder.applyImplicitNamingStrategy(
-                                org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl.INSTANCE);
-                        break;
-                    case "component-path":
-                        builder.applyImplicitNamingStrategy(
-                                org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl.INSTANCE);
-                        break;
+                    case 'default':
+                    case 'jpa':
+                        builder.applyImplicitNamingStrategy(ImplicitNamingStrategyJpaCompliantImpl.INSTANCE)
+                        break
+                    case 'legacy-hbm':
+                        builder.applyImplicitNamingStrategy(ImplicitNamingStrategyLegacyHbmImpl.INSTANCE)
+                        break
+                    case 'legacy-jpa':
+                        builder.applyImplicitNamingStrategy(ImplicitNamingStrategyLegacyJpaImpl.INSTANCE)
+                        break
+                    case 'component-path':
+                        builder.applyImplicitNamingStrategy(ImplicitNamingStrategyComponentPathImpl.INSTANCE)
+                        break
                     default:
                         builder.applyImplicitNamingStrategy((ImplicitNamingStrategy) Thread.currentThread()
                                 .getContextClassLoader()
                                 .loadClass(namingStrategy)
                                 .getDeclaredConstructor()
-                                .newInstance());
-                        break;
+                                .newInstance())
+                        break
                 }
             }
         } catch (InstantiationException |
@@ -300,7 +308,7 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
                  InvocationTargetException |
                  NoSuchMethodException |
                  ClassNotFoundException e) {
-            throw new DatabaseException(e);
+            throw new DatabaseException(e)
         }
     }
 
@@ -309,7 +317,7 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
      */
     protected void afterSetup() {
         if (dialect instanceof MySQLDialect) {
-            indexesForForeignKeys = true;
+            indexesForForeignKeys = true
         }
     }
 
@@ -317,72 +325,73 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
      * Called by {@link #buildMetadataFromPath()} to do final configuration on the {@link MetadataBuilder} before {@link MetadataBuilder#build()} is called.
      */
     protected void configureMetadataBuilder(MetadataBuilder metadataBuilder) throws DatabaseException {
-        configureImplicitNamingStrategy(getProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY), metadataBuilder);
-        configurePhysicalNamingStrategy(getProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY), metadataBuilder);
+        configureImplicitNamingStrategy(getProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY), metadataBuilder)
+        configurePhysicalNamingStrategy(getProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY), metadataBuilder)
         metadataBuilder.enableGlobalNationalizedCharacterDataSupport(
-                Boolean.parseBoolean(getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA)));
+                Boolean.parseBoolean(getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA)))
     }
 
     /**
      * Returns the value of the given property. Should return the value given as a connection URL first, then fall back to configuration-specific values.
      */
-    public String getProperty(String name) {
-        return getHibernateConnection().getProperties().getProperty(name);
+    String getProperty(String name) {
+        return getHibernateConnection().getProperties().getProperty(name)
     }
 
     /** Required for snapshot auto-increment detection of identity/sequence columns managed by Hibernate. */
     @Override
-    public boolean supportsAutoIncrement() {
-        return true;
+    boolean supportsAutoIncrement() {
+        return true
     }
 
     @Override
-    public boolean createsIndexesForForeignKeys() {
-        return indexesForForeignKeys;
+    boolean createsIndexesForForeignKeys() {
+        return indexesForForeignKeys
     }
 
     @Override
-    public Integer getDefaultPort() {
-        return 0;
+    Integer getDefaultPort() {
+        return 0
     }
 
     @Override
-    public boolean supportsInitiallyDeferrableColumns() {
-        return false;
+    boolean supportsInitiallyDeferrableColumns() {
+        return false
     }
 
     @Override
-    public boolean supportsTablespaces() {
-        return false;
+    boolean supportsTablespaces() {
+        return false
     }
 
     @Override
     protected String getConnectionCatalogName() {
-        return getDefaultCatalogName();
+        return getDefaultCatalogName()
     }
 
     @Override
     protected String getConnectionSchemaName() {
-        return getDefaultSchemaName();
+        return getDefaultSchemaName()
     }
 
     @Override
-    public String getDefaultSchemaName() {
-        return DEFAULT_SCHEMA;
+    String getDefaultSchemaName() {
+        return DEFAULT_SCHEMA
     }
 
     @Override
-    public String getDefaultCatalogName() {
-        return DEFAULT_SCHEMA;
+    String getDefaultCatalogName() {
+        return DEFAULT_SCHEMA
     }
 
     @Override
-    public boolean isSafeToRunUpdate() {
-        return true;
+    boolean isSafeToRunUpdate() {
+        return true
     }
 
     @Override
-    public boolean isCaseSensitive() {
-        return false;
+    boolean isCaseSensitive() {
+        return false
     }
+
 }
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.groovy
new file mode 100644
index 0000000000..0eec12a70a
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.groovy
@@ -0,0 +1,175 @@
+package liquibase.ext.hibernate.database
+
+import java.lang.reflect.Field
+
+import jakarta.persistence.EntityManagerFactory
+import jakarta.persistence.PersistenceUnitTransactionType
+import jakarta.persistence.metamodel.ManagedType
+import jakarta.persistence.metamodel.Metamodel
+
+import groovy.transform.CompileStatic
+import groovy.transform.stc.POJO
+import liquibase.Scope
+import liquibase.database.DatabaseConnection
+import liquibase.exception.DatabaseException
+import org.hibernate.boot.Metadata
+import org.hibernate.boot.MetadataSources
+import org.hibernate.cfg.AvailableSettings
+import org.hibernate.dialect.Dialect
+import org.hibernate.jpa.HibernatePersistenceProvider
+import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl
+import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder
+import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor
+
+/**
+ * Database implementation for "ejb3" hibernate configurations.
+ */
+@CompileStatic
+@POJO // see HibernateDatabase
+class HibernateEjb3Database extends HibernateDatabase {
+
+    protected EntityManagerFactory entityManagerFactory
+
+    @Override
+    String getShortName() {
+        return 'hibernateEjb3'
+    }
+
+    @Override
+    protected String getDefaultDatabaseProductName() {
+        return 'Hibernate EJB3'
+    }
+
+    @Override
+    boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
+        String url = conn.getURL()
+        return url != null && url.startsWith('hibernate:ejb3:')
+    }
+
+    /**
+     * Calls {@link #createEntityManagerFactoryBuilder()} to create and save the entity manager factory.
+     */
+    @Override
+    protected Metadata buildMetadataFromPath() throws DatabaseException {
+        EntityManagerFactoryBuilderImpl builder = createEntityManagerFactoryBuilder()
+        this.entityManagerFactory = builder.build()
+        Metadata metadata = builder.getMetadata()
+
+        // this.@dialect: direct access to the inherited protected field. A bare assignment would compile
+        // to a dynamic property set, which a @POJO class cannot service.
+        String dialectString = findDialectName()
+        if (dialectString != null) {
+            try {
+                this.@dialect = (Dialect) Class.forName(dialectString)
+                        .getDeclaredConstructor()
+                        .newInstance()
+                Scope.getCurrentScope().getLog(getClass()).info('Using dialect ' + dialectString)
+            } catch (Exception e) {
+                throw new DatabaseException(e)
+            }
+        } else {
+            Scope.getCurrentScope()
+                    .getLog(getClass())
+                    .info('Could not determine hibernate dialect, using HibernateGenericDialect')
+            this.@dialect = new HibernateGenericDialect()
+        }
+
+        return metadata
+    }
+
+    protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() {
+        MyHibernatePersistenceProvider persistenceProvider = new MyHibernatePersistenceProvider()
+
+        Map<String, Object> properties = new HashMap<>()
+        properties.put(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString())
+        properties.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE.toString())
+        properties.put(
+                AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA,
+                getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA))
+
+        String path = getHibernateConnection().getPath()
+        if (path == null) {
+            throw new IllegalStateException('Hibernate connection path is null')
+        }
+
+        return (EntityManagerFactoryBuilderImpl) persistenceProvider.getEntityManagerFactoryBuilderOrNull(
+                path, properties, null)
+    }
+
+    @Override
+    String getProperty(String name) {
+        if (entityManagerFactory != null) {
+            String value = (String) entityManagerFactory.getProperties().get(name)
+            if (value != null) {
+                return value
+            }
+        }
+        return super.getProperty(name)
+    }
+
+    @Override
+    protected String findDialectName() {
+        String dialectName = super.findDialectName()
+        if (dialectName != null) {
+            return dialectName
+        }
+        return entityManagerFactory != null ?
+                (String) entityManagerFactory.getProperties().get(AvailableSettings.DIALECT) : null
+    }
+
+    /**
+     * Adds sources based on what is in the saved entityManagerFactory
+     */
+    @Override
+    protected void configureSources(MetadataSources sources) {
+        Metamodel metamodel = entityManagerFactory != null ? entityManagerFactory.getMetamodel() : null
+        if (metamodel != null) {
+            for (ManagedType<?> managedType : metamodel.getManagedTypes()) {
+                Class<?> javaType = managedType.getJavaType()
+                if (javaType != null) {
+                    sources.addAnnotatedClass(javaType)
+                }
+            }
+        }
+
+        for (Package pkg : Package.getPackages()) {
+            sources.addPackage(pkg)
+        }
+    }
+
+    private static class MyHibernatePersistenceProvider extends HibernatePersistenceProvider {
+
+        private void setField(final Object obj, String fieldName, final Object value)
+                throws NoSuchFieldException, IllegalAccessException {
+            final Field declaredField = obj.getClass().getDeclaredField(fieldName)
+            if (declaredField.trySetAccessible()) {
+                declaredField.set(obj, value)
+            } else {
+                throw new IllegalAccessException('Cannot access field: ' + fieldName)
+            }
+        }
+
+        // Re-declared so the protected superclass method becomes accessible from the enclosing
+        // class, which is in this package but is not a HibernatePersistenceProvider subclass.
+        @Override
+        @SuppressWarnings('UnnecessaryOverridingMethod')
+        protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(
+                String persistenceUnitName, Map properties, ClassLoader providedClassLoader) {
+            return super.getEntityManagerFactoryBuilderOrNull(persistenceUnitName, properties, providedClassLoader)
+        }
+
+        @Override
+        protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(
+                PersistenceUnitDescriptor persistenceUnitDescriptor, Map integration, ClassLoader providedClassLoader) {
+            try {
+                setField(persistenceUnitDescriptor, 'jtaDataSource', null)
+                setField(persistenceUnitDescriptor, 'transactionType', PersistenceUnitTransactionType.RESOURCE_LOCAL)
+            } catch (Exception ex) {
+                Scope.getCurrentScope().getLog(getClass()).severe(null, ex)
+            }
+            return super.getEntityManagerFactoryBuilder(persistenceUnitDescriptor, integration, providedClassLoader)
+        }
+
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.java
deleted file mode 100644
index 46e189d9a4..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateEjb3Database.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-import jakarta.persistence.EntityManagerFactory;
-import jakarta.persistence.PersistenceUnitTransactionType;
-import jakarta.persistence.metamodel.ManagedType;
-
-import liquibase.Scope;
-import liquibase.database.DatabaseConnection;
-import liquibase.exception.DatabaseException;
-import org.hibernate.boot.Metadata;
-import org.hibernate.boot.MetadataSources;
-import org.hibernate.cfg.AvailableSettings;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.jpa.HibernatePersistenceProvider;
-import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
-import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder;
-import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
-
-/**
- * Database implementation for "ejb3" hibernate configurations.
- */
-public class HibernateEjb3Database extends HibernateDatabase {
-
-    protected EntityManagerFactory entityManagerFactory;
-
-    @Override
-    public String getShortName() {
-        return "hibernateEjb3";
-    }
-
-    @Override
-    protected String getDefaultDatabaseProductName() {
-        return "Hibernate EJB3";
-    }
-
-    @Override
-    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
-        return Optional.ofNullable(conn.getURL())
-                .map(url -> url.startsWith("hibernate:ejb3:"))
-                .orElse(false);
-    }
-
-    /**
-     * Calls {@link #createEntityManagerFactoryBuilder()} to create and save the entity manager factory.
-     */
-    @Override
-    protected Metadata buildMetadataFromPath() throws DatabaseException {
-        EntityManagerFactoryBuilderImpl builder = createEntityManagerFactoryBuilder();
-        this.entityManagerFactory = builder.build();
-        Metadata metadata = builder.getMetadata();
-
-        String dialectString = findDialectName();
-        if (dialectString != null) {
-            try {
-                dialect = (Dialect) Class.forName(dialectString)
-                        .getDeclaredConstructor()
-                        .newInstance();
-                Scope.getCurrentScope().getLog(getClass()).info("Using dialect " + dialectString);
-            } catch (Exception e) {
-                throw new DatabaseException(e);
-            }
-        } else {
-            Scope.getCurrentScope()
-                    .getLog(getClass())
-                    .info("Could not determine hibernate dialect, using HibernateGenericDialect");
-            dialect = new HibernateGenericDialect();
-        }
-
-        return metadata;
-    }
-
-    protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() {
-        MyHibernatePersistenceProvider persistenceProvider = new MyHibernatePersistenceProvider();
-
-        Map<String, Object> properties = new HashMap<>();
-        properties.put(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString());
-        properties.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE.toString());
-        properties.put(
-                AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA,
-                getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA));
-
-        String path = Optional.ofNullable(getHibernateConnection().getPath())
-                .orElseThrow(() -> new IllegalStateException("Hibernate connection path is null"));
-
-        return (EntityManagerFactoryBuilderImpl) persistenceProvider.getEntityManagerFactoryBuilderOrNull(
-                path, properties, null);
-    }
-
-    @Override
-    public String getProperty(String name) {
-        return Optional.ofNullable(entityManagerFactory)
-                .map(emf -> (String) emf.getProperties().get(name))
-                .or(() -> Optional.ofNullable(super.getProperty(name)))
-                .orElse(null);
-    }
-
-    @Override
-    protected String findDialectName() {
-        return Optional.ofNullable(super.findDialectName())
-                .or(() -> Optional.ofNullable(entityManagerFactory)
-                        .map(emf -> (String) emf.getProperties().get(AvailableSettings.DIALECT)))
-                .orElse(null);
-    }
-
-    /**
-     * Adds sources based on what is in the saved entityManagerFactory
-     */
-    @Override
-    protected void configureSources(MetadataSources sources) {
-        Optional.ofNullable(entityManagerFactory)
-                .map(EntityManagerFactory::getMetamodel)
-                .ifPresent(metamodel -> metamodel.getManagedTypes().stream()
-                        .map(ManagedType::getJavaType)
-                        .filter(java.util.Objects::nonNull)
-                        .forEach(sources::addAnnotatedClass));
-
-        Arrays.stream(Package.getPackages()).forEach(sources::addPackage);
-    }
-
-    private static class MyHibernatePersistenceProvider extends HibernatePersistenceProvider {
-
-        private void setField(final Object obj, String fieldName, final Object value)
-                throws NoSuchFieldException, IllegalAccessException {
-            final Field declaredField = obj.getClass().getDeclaredField(fieldName);
-            if (declaredField.trySetAccessible()) {
-                declaredField.set(obj, value);
-            } else {
-                throw new IllegalAccessException("Cannot access field: " + fieldName);
-            }
-        }
-
-        @Override
-        protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(
-                String persistenceUnitName, Map properties, ClassLoader providedClassLoader) {
-            return super.getEntityManagerFactoryBuilderOrNull(persistenceUnitName, properties, providedClassLoader);
-        }
-
-        @Override
-        protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(
-                PersistenceUnitDescriptor persistenceUnitDescriptor, Map integration, ClassLoader providedClassLoader) {
-            try {
-                setField(persistenceUnitDescriptor, "jtaDataSource", null);
-                setField(persistenceUnitDescriptor, "transactionType", PersistenceUnitTransactionType.RESOURCE_LOCAL);
-            } catch (Exception ex) {
-                Scope.getCurrentScope().getLog(getClass()).severe(null, ex);
-            }
-            return super.getEntityManagerFactoryBuilder(persistenceUnitDescriptor, integration, providedClassLoader);
-        }
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.groovy
new file mode 100644
index 0000000000..14e3f0a409
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.groovy
@@ -0,0 +1,17 @@
+package liquibase.ext.hibernate.database
+
+import groovy.transform.CompileStatic
+import org.hibernate.dialect.DatabaseVersion
+import org.hibernate.dialect.Dialect
+
+/**
+ * Generic hibernate dialect used when an actual dialect cannot be determined.
+ */
+@CompileStatic
+class HibernateGenericDialect extends Dialect {
+
+    HibernateGenericDialect() {
+        super(DatabaseVersion.make(7, 2))
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.java
deleted file mode 100644
index f8f5c9b1de..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateGenericDialect.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import org.hibernate.dialect.DatabaseVersion;
-import org.hibernate.dialect.Dialect;
-
-/**
- * Generic hibernate dialect used when an actual dialect cannot be determined.
- */
-public class HibernateGenericDialect extends Dialect {
-    public HibernateGenericDialect() {
-        super(DatabaseVersion.make(7, 2));
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.groovy
new file mode 100644
index 0000000000..1df1cc0569
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.groovy
@@ -0,0 +1,205 @@
+package liquibase.ext.hibernate.database
+
+import groovy.transform.CompileStatic
+import groovy.transform.stc.POJO
+import liquibase.Scope
+import liquibase.database.DatabaseConnection
+import liquibase.exception.DatabaseException
+import liquibase.ext.hibernate.database.connection.HibernateConnection
+import org.hibernate.boot.Metadata
+import org.hibernate.boot.MetadataSources
+import org.springframework.beans.MutablePropertyValues
+import org.springframework.beans.PropertyValue
+import org.springframework.beans.factory.NoSuchBeanDefinitionException
+import org.springframework.beans.factory.config.BeanDefinition
+import org.springframework.beans.factory.config.TypedStringValue
+import org.springframework.beans.factory.support.BeanDefinitionRegistry
+import org.springframework.beans.factory.support.ManagedProperties
+import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader
+import org.springframework.core.io.ClassPathResource
+import org.springframework.core.io.Resource
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver
+import org.springframework.core.io.support.ResourcePatternResolver
+
+/**
+ * Database implementation for "spring" hibernate configurations where a bean name is given. If a package is used, {@link HibernateSpringPackageDatabase} will be used.
+ */
+@CompileStatic
+@POJO // see HibernateDatabase
+class HibernateSpringBeanDatabase extends HibernateDatabase {
+
+    private BeanDefinition beanDefinition
+    private ManagedProperties beanDefinitionProperties
+
+    @Override
+    boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
+        String url = conn.getURL()
+        return url != null && url.startsWith('hibernate:spring:')
+    }
+
+    /**
+     * Calls {@link #loadBeanDefinition()}
+     */
+    @Override
+    protected Metadata buildMetadataFromPath() throws DatabaseException {
+        loadBeanDefinition()
+        return super.buildMetadataFromPath()
+    }
+
+    @Override
+    String getProperty(String name) {
+        String value = super.getProperty(name)
+        if (value != null) {
+            return value
+        }
+        String fromBeanDefinition = findPropertyInBeanDefinition(name)
+        if (fromBeanDefinition != null) {
+            return fromBeanDefinition
+        }
+        return beanDefinitionProperties != null ? beanDefinitionProperties.getProperty(name) : null
+    }
+
+    private String findPropertyInBeanDefinition(String name) {
+        if (beanDefinitionProperties == null) {
+            return null
+        }
+        for (Map.Entry<Object, Object> entry : beanDefinitionProperties.entrySet()) {
+            if (name.equals(resolveString(entry.getKey()))) {
+                String value = resolveString(entry.getValue())
+                if (value != null) {
+                    return value
+                }
+            }
+        }
+        return null
+    }
+
+    private String resolveString(Object obj) {
+        if (obj instanceof TypedStringValue) {
+            return ((TypedStringValue) obj).getValue()
+        } else if (obj instanceof String) {
+            return (String) obj
+        }
+        return null
+    }
+
+    /**
+     * Parse the given URL assuming it is a spring XML file
+     */
+    protected void loadBeanDefinition() {
+        // Read configuration
+        BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry()
+        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry)
+        reader.setNamespaceAware(true)
+
+        // Fix: Use try-with-resources to ensure HibernateConnection is closed (PMD #8)
+        HibernateConnection connection = getHibernateConnection()
+        try {
+            String path = connection.getPath()
+            if (path == null) {
+                throw new IllegalStateException('Hibernate connection path is null')
+            }
+            reader.loadBeanDefinitions(new ClassPathResource(path))
+
+            Properties props = connection.getProperties()
+            if (props == null) {
+                throw new IllegalStateException('Hibernate connection properties are null')
+            }
+
+            String beanName = props.getProperty('bean')
+            if (beanName == null) {
+                throw new IllegalStateException("A 'bean' name is required, definition in '" + path + "'.")
+            }
+
+            try {
+                beanDefinition = registry.getBeanDefinition(beanName)
+                PropertyValue hibernateProperties = beanDefinition.getPropertyValues().getPropertyValue('hibernateProperties')
+                Object propertiesValue = hibernateProperties != null ? hibernateProperties.getValue() : null
+                if (propertiesValue instanceof ManagedProperties) {
+                    beanDefinitionProperties = (ManagedProperties) propertiesValue
+                }
+            } catch (NoSuchBeanDefinitionException e) {
+                throw new IllegalStateException(
+                        "A bean named '" + beanName + "' could not be found in '" + path + "'.", e)
+            }
+        } finally {
+            connection.close()
+        }
+    }
+
+    @Override
+    protected void configureSources(MetadataSources sources) throws DatabaseException {
+        BeanDefinition bd = beanDefinition
+        if (bd == null) {
+            throw new DatabaseException('Bean definition is not loaded.')
+        }
+        MutablePropertyValues properties = bd.getPropertyValues()
+
+        // Add annotated classes list.
+        for (String className : extractListProperty(properties, 'annotatedClasses')) {
+            Scope.getCurrentScope().getLog(getClass()).info('Found annotated class ' + className)
+            sources.addAnnotatedClass(findClass(className))
+        }
+
+        // Add mapping locations
+        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver()
+        for (String mappingLocation : extractListProperty(properties, 'mappingLocations')) {
+            try {
+                Scope.getCurrentScope().getLog(getClass()).info('Found mappingLocation ' + mappingLocation)
+                Resource[] resources = resourcePatternResolver.getResources(mappingLocation)
+                for (Resource resource : resources) {
+                    URL url = resource.getURL()
+                    Scope.getCurrentScope().getLog(getClass()).info('Adding resource  ' + url)
+                    sources.addURL(url)
+                }
+            } catch (IOException e) {
+                // Fix: Pass 'e' as cause to preserve stack trace (PMD #9)
+                throw new RuntimeException('Error resolving mapping location: ' + mappingLocation, e)
+            }
+        }
+    }
+
+    private List<String> extractListProperty(MutablePropertyValues properties, String propertyName) {
+        List<String> result = new ArrayList<>()
+        PropertyValue propertyValue = properties.getPropertyValue(propertyName)
+        Object value = propertyValue != null ? propertyValue.getValue() : null
+        if (value instanceof List) {
+            for (Object item : (List<?>) value) {
+                if (item instanceof TypedStringValue) {
+                    String stringValue = ((TypedStringValue) item).getValue()
+                    if (stringValue != null) {
+                        result.add(stringValue)
+                    }
+                }
+            }
+        }
+        return result
+    }
+
+    private Class<?> findClass(String className) {
+        try {
+            Class<?> newClass = Class.forName(className)
+            if (Object.isAssignableFrom(newClass)) {
+                return newClass.asSubclass(Object)
+            } else {
+                throw new IllegalStateException("The provided class '" + className + "' is not assignable from the '" +
+                        Object.getName() + "' superclass.")
+            }
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException(
+                    "Unable to find required class: '" + className + "'. Please check classpath and class name.", e)
+        }
+    }
+
+    @Override
+    String getShortName() {
+        return 'hibernateSpringBean'
+    }
+
+    @Override
+    protected String getDefaultDatabaseProductName() {
+        return 'Hibernate Spring Bean'
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java
deleted file mode 100644
index 419c3c7f09..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java
+++ /dev/null
@@ -1,184 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.List;
-import java.util.Optional;
-import java.util.Properties;
-import java.util.stream.Stream;
-
-import liquibase.Scope;
-import liquibase.database.DatabaseConnection;
-import liquibase.exception.DatabaseException;
-import liquibase.ext.hibernate.database.connection.HibernateConnection;
-import org.hibernate.boot.Metadata;
-import org.hibernate.boot.MetadataSources;
-
-import org.springframework.beans.MutablePropertyValues;
-import org.springframework.beans.PropertyValue;
-import org.springframework.beans.factory.NoSuchBeanDefinitionException;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.TypedStringValue;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.beans.factory.support.ManagedProperties;
-import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
-import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
-import org.springframework.core.io.support.ResourcePatternResolver;
-
-/**
- * Database implementation for "spring" hibernate configurations where a bean name is given. If a package is used, {@link HibernateSpringPackageDatabase} will be used.
- */
-public class HibernateSpringBeanDatabase extends HibernateDatabase {
-
-    private BeanDefinition beanDefinition;
-    private ManagedProperties beanDefinitionProperties;
-
-    @Override
-    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
-        return Optional.ofNullable(conn.getURL())
-                .map(url -> url.startsWith("hibernate:spring:"))
-                .orElse(false);
-    }
-
-    /**
-     * Calls {@link #loadBeanDefinition()}
-     */
-    @Override
-    protected Metadata buildMetadataFromPath() throws DatabaseException {
-        loadBeanDefinition();
-        return super.buildMetadataFromPath();
-    }
-
-    @Override
-    public String getProperty(String name) {
-        return Optional.ofNullable(super.getProperty(name))
-                .or(() -> findPropertyInBeanDefinition(name))
-                .orElseGet(() -> beanDefinitionProperties != null ? beanDefinitionProperties.getProperty(name) : null);
-    }
-
-    private Optional<String> findPropertyInBeanDefinition(String name) {
-        return Optional.ofNullable(beanDefinitionProperties)
-                .flatMap(props -> props.entrySet().stream()
-                        .filter(entry -> name.equals(resolveString(entry.getKey())))
-                        .map(entry -> resolveString(entry.getValue()))
-                        .filter(java.util.Objects::nonNull)
-                        .findFirst());
-    }
-
-    private String resolveString(Object obj) {
-        if (obj instanceof TypedStringValue tsv) {
-            return tsv.getValue();
-        } else if (obj instanceof String s) {
-            return s;
-        }
-        return null;
-    }
-
-    /**
-     * Parse the given URL assuming it is a spring XML file
-     */
-    protected void loadBeanDefinition() {
-        // Read configuration
-        BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
-        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
-        reader.setNamespaceAware(true);
-
-        // Fix: Use try-with-resources to ensure HibernateConnection is closed (PMD #8)
-        try (HibernateConnection connection = getHibernateConnection()) {
-            String path = Optional.ofNullable(connection.getPath())
-                    .orElseThrow(() -> new IllegalStateException("Hibernate connection path is null"));
-            reader.loadBeanDefinitions(new ClassPathResource(path));
-
-            Properties props = Optional.ofNullable(connection.getProperties())
-                    .orElseThrow(() -> new IllegalStateException("Hibernate connection properties are null"));
-
-            String beanName = Optional.ofNullable(props.getProperty("bean"))
-                    .orElseThrow(() -> new IllegalStateException("A 'bean' name is required, definition in '" + path + "'."));
-
-            try {
-                beanDefinition = registry.getBeanDefinition(beanName);
-                Optional.ofNullable(beanDefinition.getPropertyValues().getPropertyValue("hibernateProperties"))
-                        .map(PropertyValue::getValue)
-                        .filter(ManagedProperties.class::isInstance)
-                        .map(ManagedProperties.class::cast)
-                        .ifPresent(p -> beanDefinitionProperties = p);
-            } catch (NoSuchBeanDefinitionException e) {
-                throw new IllegalStateException(
-                        "A bean named '" + beanName + "' could not be found in '" + path + "'.", e);
-            }
-        }
-    }
-
-    @Override
-    protected void configureSources(MetadataSources sources) throws DatabaseException {
-        BeanDefinition bd = Optional.ofNullable(beanDefinition)
-                .orElseThrow(() -> new DatabaseException("Bean definition is not loaded."));
-        MutablePropertyValues properties = bd.getPropertyValues();
-
-        // Add annotated classes list.
-        extractListProperty(properties, "annotatedClasses")
-                .forEach(className -> {
-                    Scope.getCurrentScope().getLog(getClass()).info("Found annotated class " + className);
-                    sources.addAnnotatedClass(findClass(className));
-                });
-
-        // Add mapping locations
-        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
-        extractListProperty(properties, "mappingLocations")
-                .forEach(mappingLocation -> {
-                    try {
-                        Scope.getCurrentScope().getLog(getClass()).info("Found mappingLocation " + mappingLocation);
-                        Resource[] resources = resourcePatternResolver.getResources(mappingLocation);
-                        for (Resource resource : resources) {
-                            URL url = resource.getURL();
-                            Scope.getCurrentScope().getLog(getClass()).info("Adding resource  " + url);
-                            sources.addURL(url);
-                        }
-                    } catch (IOException e) {
-                        // Fix: Pass 'e' as cause to preserve stack trace (PMD #9)
-                        throw new RuntimeException("Error resolving mapping location: " + mappingLocation, e);
-                    }
-                });
-    }
-
-    private Stream<String> extractListProperty(MutablePropertyValues properties, String propertyName) {
-        return Optional.ofNullable(properties.getPropertyValue(propertyName))
-                .map(PropertyValue::getValue)
-                .filter(List.class::isInstance)
-                .map(v -> (List<?>) v)
-                .stream()
-                .flatMap(List::stream)
-                .filter(TypedStringValue.class::isInstance)
-                .map(TypedStringValue.class::cast)
-                .map(TypedStringValue::getValue)
-                .filter(java.util.Objects::nonNull);
-    }
-
-    private Class<?> findClass(String className) {
-        try {
-            Class<?> newClass = Class.forName(className);
-            if (Object.class.isAssignableFrom(newClass)) {
-                return newClass.asSubclass(Object.class);
-            } else {
-                throw new IllegalStateException("The provided class '" + className + "' is not assignable from the '" +
-                        Object.class.getName() + "' superclass.");
-            }
-        } catch (ClassNotFoundException e) {
-            throw new IllegalStateException(
-                    "Unable to find required class: '" + className + "'. Please check classpath and class name.", e);
-        }
-    }
-
-    @Override
-    public String getShortName() {
-        return "hibernateSpringBean";
-    }
-
-    @Override
-    protected String getDefaultDatabaseProductName() {
-        return "Hibernate Spring Bean";
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.groovy
new file mode 100644
index 0000000000..b517da64fa
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.groovy
@@ -0,0 +1,176 @@
+package liquibase.ext.hibernate.database
+
+import java.sql.Connection
+
+import jakarta.persistence.spi.PersistenceUnitInfo
+
+import groovy.transform.CompileStatic
+import groovy.transform.stc.POJO
+import liquibase.Scope
+import liquibase.database.DatabaseConnection
+import liquibase.database.jvm.JdbcConnection
+import liquibase.ext.hibernate.database.connection.HibernateConnection
+import org.hibernate.bytecode.enhance.spi.EnhancementContext
+import org.hibernate.cfg.AvailableSettings
+import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl
+import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor
+import org.hibernate.jpa.boot.spi.Bootstrap
+import org.springframework.core.NativeDetector
+import org.springframework.core.io.ClassPathResource
+import org.springframework.core.io.DefaultResourceLoader
+import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager
+import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
+
+/**
+ * Database implementation for "spring" hibernate configurations that scans packages. If specifying a bean, {@link HibernateSpringBeanDatabase} is used.
+ */
+@CompileStatic
+@POJO // see HibernateDatabase
+class HibernateSpringPackageDatabase extends JpaPersistenceDatabase {
+
+    // Literal copies of org.hibernate.envers.configuration.EnversSettings constants. hibernate-envers is
+    // compileOnly: javac inlined these compile-time constants, whereas Groovy would emit a GETSTATIC that
+    // loads EnversSettings at runtime and fails when Envers is not on the classpath.
+    private static final String ENVERS_AUDIT_TABLE_PREFIX = 'org.hibernate.envers.audit_table_prefix'
+    private static final String ENVERS_AUDIT_TABLE_SUFFIX = 'org.hibernate.envers.audit_table_suffix'
+    private static final String ENVERS_REVISION_FIELD_NAME = 'org.hibernate.envers.revision_field_name'
+    private static final String ENVERS_REVISION_TYPE_FIELD_NAME = 'org.hibernate.envers.revision_type_field_name'
+
+    @Override
+    boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
+        String url = conn.getURL()
+        return url != null && url.startsWith('hibernate:spring:') && !isXmlFile(conn)
+    }
+
+    @Override
+    int getPriority() {
+        return super.getPriority() + 10 // want this to be picked over HibernateSpringBeanDatabase if it is not xml file
+    }
+
+    /**
+     * Return true if the given path is a spring XML file.
+     */
+    protected boolean isXmlFile(DatabaseConnection connection) {
+        HibernateConnection hibernateConnection = getHibernateConnection(connection)
+
+        if (hibernateConnection == null || hibernateConnection.getPath() == null) {
+            return false
+        }
+
+        String path = hibernateConnection.getPath()
+
+        // If it looks like a path, treat as XML
+        if (path.contains('/')) {
+            return true
+        }
+
+        // Use Context ClassLoader for resource lookup (PMD #11 compliance)
+        ClassLoader loader = Thread.currentThread().getContextClassLoader()
+        ClassPathResource resource = new ClassPathResource(path, loader)
+
+        try {
+            return resource.exists() && !resource.getFile().isDirectory()
+        } catch (IOException e) {
+            return false
+        }
+    }
+
+    private HibernateConnection getHibernateConnection(DatabaseConnection conn) {
+        if (conn instanceof HibernateConnection) {
+            return (HibernateConnection) conn
+        }
+        if (conn instanceof JdbcConnection) {
+            Connection underlying = ((JdbcConnection) conn).getUnderlyingConnection()
+            if (underlying instanceof HibernateConnection) {
+                return (HibernateConnection) underlying
+            }
+        }
+        return null
+    }
+
+    @Override
+    protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() {
+        DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager()
+
+        // Fix: Use Thread Context ClassLoader for J2EE/Spring compliance (PMD #11)
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader()
+        internalPersistenceUnitManager.setResourceLoader(new DefaultResourceLoader(contextClassLoader))
+
+        // Fix: Use try-with-resources to ensure connection is handled correctly (PMD #10)
+        HibernateConnection connection = getHibernateConnection()
+        try {
+            String path = connection.getPath()
+            if (path == null) {
+                throw new IllegalStateException('Hibernate connection path is null')
+            }
+            String[] packagesToScan = path.split(',')
+
+            for (String packageName : packagesToScan) {
+                Scope.getCurrentScope().getLog(getClass()).info('Found package ' + packageName)
+            }
+
+            internalPersistenceUnitManager.setPackagesToScan(packagesToScan)
+            internalPersistenceUnitManager.preparePersistenceUnitInfos()
+            PersistenceUnitInfo persistenceUnitInfo = internalPersistenceUnitManager.obtainDefaultPersistenceUnitInfo()
+            HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter()
+
+            if (persistenceUnitInfo instanceof SmartPersistenceUnitInfo) {
+                ((SmartPersistenceUnitInfo) persistenceUnitInfo)
+                        .setPersistenceProviderPackageName(jpaVendorAdapter.getPersistenceProviderRootPackage())
+            }
+
+            Map<String, Object> map = new HashMap<>()
+            map.put(AvailableSettings.DIALECT, getProperty(AvailableSettings.DIALECT))
+            map.put(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString())
+            map.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE.toString())
+            map.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, connection.getProperties().getProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY))
+            map.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, connection.getProperties().getProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY))
+            map.put(AvailableSettings.SCANNER_DISCOVERY, '')
+            map.put(ENVERS_AUDIT_TABLE_PREFIX, connection.getProperties().getProperty(ENVERS_AUDIT_TABLE_PREFIX, ''))
+            map.put(ENVERS_AUDIT_TABLE_SUFFIX, connection.getProperties().getProperty(ENVERS_AUDIT_TABLE_SUFFIX, '_AUD'))
+            map.put(ENVERS_REVISION_FIELD_NAME, connection.getProperties().getProperty(ENVERS_REVISION_FIELD_NAME, 'REV'))
+            map.put(ENVERS_REVISION_TYPE_FIELD_NAME, connection.getProperties().getProperty(ENVERS_REVISION_TYPE_FIELD_NAME, 'REVTYPE'))
+            map.put(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA, getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA))
+            map.put(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, getProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE))
+
+            PersistenceUnitInfoDescriptor persistenceUnitInfoDescriptor = createPersistenceUnitInfoDescriptor(persistenceUnitInfo)
+            return (EntityManagerFactoryBuilderImpl) Bootstrap.getEntityManagerFactoryBuilder(persistenceUnitInfoDescriptor, map)
+        } finally {
+            connection.close()
+        }
+    }
+
+    PersistenceUnitInfoDescriptor createPersistenceUnitInfoDescriptor(PersistenceUnitInfo info) {
+        final List<String> mergedClassesAndPackages = new ArrayList<>(info.getManagedClassNames())
+        if (info instanceof SmartPersistenceUnitInfo) {
+            mergedClassesAndPackages.addAll(((SmartPersistenceUnitInfo) info).getManagedPackages())
+        }
+        return new PersistenceUnitInfoDescriptor(info) {
+
+            @Override
+            List<String> getManagedClassNames() {
+                return mergedClassesAndPackages
+            }
+
+            @Override
+            void pushClassTransformer(EnhancementContext enhancementContext) {
+                if (!NativeDetector.inNativeImage()) {
+                    super.pushClassTransformer(enhancementContext)
+                }
+            }
+
+        }
+    }
+
+    @Override
+    String getShortName() {
+        return 'hibernateSpringPackage'
+    }
+
+    @Override
+    protected String getDefaultDatabaseProductName() {
+        return 'Hibernate Spring Package'
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java
deleted file mode 100644
index 2c4712d0fc..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import jakarta.persistence.spi.PersistenceUnitInfo;
-
-import liquibase.Scope;
-import liquibase.database.DatabaseConnection;
-import liquibase.ext.hibernate.database.connection.HibernateConnection;
-import org.hibernate.bytecode.enhance.spi.EnhancementContext;
-import org.hibernate.cfg.AvailableSettings;
-import org.hibernate.envers.configuration.EnversSettings;
-import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
-import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
-import org.hibernate.jpa.boot.spi.Bootstrap;
-
-import org.springframework.core.NativeDetector;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.DefaultResourceLoader;
-import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
-import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo;
-import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
-
-/**
- * Database implementation for "spring" hibernate configurations that scans packages. If specifying a bean, {@link HibernateSpringBeanDatabase} is used.
- */
-public class HibernateSpringPackageDatabase extends JpaPersistenceDatabase {
-
-    @Override
-    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
-        return Optional.ofNullable(conn.getURL())
-                .map(url -> url.startsWith("hibernate:spring:") && !isXmlFile(conn))
-                .orElse(false);
-    }
-
-    @Override
-    public int getPriority() {
-        return super.getPriority() + 10; // want this to be picked over HibernateSpringBeanDatabase if it is not xml file
-    }
-
-    /**
-     * Return true if the given path is a spring XML file.
-     */
-    @SuppressWarnings("PMD.CloseResource")
-    protected boolean isXmlFile(DatabaseConnection connection) {
-        HibernateConnection hibernateConnection = getHibernateConnection(connection);
-
-        if (hibernateConnection == null || hibernateConnection.getPath() == null) {
-            return false;
-        }
-
-        String path = hibernateConnection.getPath();
-
-        // If it looks like a path, treat as XML
-        if (path.contains("/")) {
-            return true;
-        }
-
-        // Use Context ClassLoader for resource lookup (PMD #11 compliance)
-        ClassLoader loader = Thread.currentThread().getContextClassLoader();
-        ClassPathResource resource = new ClassPathResource(path, loader);
-
-        try {
-            return resource.exists() && !resource.getFile().isDirectory();
-        } catch (IOException e) {
-            return false;
-        }
-    }
-
-    private HibernateConnection getHibernateConnection(DatabaseConnection conn) {
-        if (conn instanceof HibernateConnection hc) {
-            return hc;
-        }
-        if (conn instanceof liquibase.database.jvm.JdbcConnection jdbc &&
-                jdbc.getUnderlyingConnection() instanceof HibernateConnection hc) {
-            return hc;
-        }
-        return null;
-    }
-
-    @Override
-    protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() {
-        DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager();
-
-        // Fix: Use Thread Context ClassLoader for J2EE/Spring compliance (PMD #11)
-        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-        internalPersistenceUnitManager.setResourceLoader(new DefaultResourceLoader(contextClassLoader));
-
-        // Fix: Use try-with-resources to ensure connection is handled correctly (PMD #10)
-        try (HibernateConnection connection = getHibernateConnection()) {
-            String path = connection.getPath();
-            if (path == null) {
-                throw new IllegalStateException("Hibernate connection path is null");
-            }
-            String[] packagesToScan = path.split(",");
-
-            for (String packageName : packagesToScan) {
-                Scope.getCurrentScope().getLog(getClass()).info("Found package " + packageName);
-            }
-
-            internalPersistenceUnitManager.setPackagesToScan(packagesToScan);
-            internalPersistenceUnitManager.preparePersistenceUnitInfos();
-            PersistenceUnitInfo persistenceUnitInfo = internalPersistenceUnitManager.obtainDefaultPersistenceUnitInfo();
-            HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
-
-            if (persistenceUnitInfo instanceof SmartPersistenceUnitInfo) {
-                ((SmartPersistenceUnitInfo) persistenceUnitInfo)
-                        .setPersistenceProviderPackageName(jpaVendorAdapter.getPersistenceProviderRootPackage());
-            }
-
-            Map<String, Object> map = new HashMap<>();
-            map.put(AvailableSettings.DIALECT, getProperty(AvailableSettings.DIALECT));
-            map.put(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString());
-            map.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE.toString());
-            map.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, connection.getProperties().getProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY));
-            map.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, connection.getProperties().getProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY));
-            map.put(AvailableSettings.SCANNER_DISCOVERY, "");
-            map.put(EnversSettings.AUDIT_TABLE_PREFIX, connection.getProperties().getProperty(EnversSettings.AUDIT_TABLE_PREFIX, ""));
-            map.put(EnversSettings.AUDIT_TABLE_SUFFIX, connection.getProperties().getProperty(EnversSettings.AUDIT_TABLE_SUFFIX, "_AUD"));
-            map.put(EnversSettings.REVISION_FIELD_NAME, connection.getProperties().getProperty(EnversSettings.REVISION_FIELD_NAME, "REV"));
-            map.put(EnversSettings.REVISION_TYPE_FIELD_NAME, connection.getProperties().getProperty(EnversSettings.REVISION_TYPE_FIELD_NAME, "REVTYPE"));
-            map.put(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA, getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA));
-            map.put(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, getProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE));
-
-            PersistenceUnitInfoDescriptor persistenceUnitInfoDescriptor = createPersistenceUnitInfoDescriptor(persistenceUnitInfo);
-            return (EntityManagerFactoryBuilderImpl) Bootstrap.getEntityManagerFactoryBuilder(persistenceUnitInfoDescriptor, map);
-
-        }
-    }
-
-    public PersistenceUnitInfoDescriptor createPersistenceUnitInfoDescriptor(PersistenceUnitInfo info) {
-        final List<String> mergedClassesAndPackages = new ArrayList<>(info.getManagedClassNames());
-        if (info instanceof SmartPersistenceUnitInfo) {
-            mergedClassesAndPackages.addAll(((SmartPersistenceUnitInfo) info).getManagedPackages());
-        }
-        return new PersistenceUnitInfoDescriptor(info) {
-            @Override
-            public List<String> getManagedClassNames() {
-                return mergedClassesAndPackages;
-            }
-
-            @Override
-            public void pushClassTransformer(EnhancementContext enhancementContext) {
-                if (!NativeDetector.inNativeImage()) {
-                    super.pushClassTransformer(enhancementContext);
-                }
-            }
-        };
-    }
-
-    @Override
-    public String getShortName() {
-        return "hibernateSpringPackage";
-    }
-
-    @Override
-    protected String getDefaultDatabaseProductName() {
-        return "Hibernate Spring Package";
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.groovy
new file mode 100644
index 0000000000..a59046eb47
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.groovy
@@ -0,0 +1,65 @@
+package liquibase.ext.hibernate.database
+
+import jakarta.persistence.spi.PersistenceUnitInfo
+
+import groovy.transform.CompileStatic
+import liquibase.database.DatabaseConnection
+import liquibase.ext.hibernate.database.connection.HibernateDriver
+import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl
+import org.hibernate.jpa.boot.spi.Bootstrap
+import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager
+
+/**
+ * Database implementation for JPA configurations.
+ * This supports passing a JPA persistence XML file reference.
+ */
+@CompileStatic
+class JpaPersistenceDatabase extends HibernateEjb3Database {
+
+    @Override
+    boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
+        String url = conn.getURL()
+        return url != null && url.startsWith('jpa:persistence:')
+    }
+
+    @Override
+    String getDefaultDriver(String url) {
+        if (url != null && url.startsWith('jpa:persistence:')) {
+            return HibernateDriver.getName()
+        }
+        return null
+    }
+
+    @Override
+    String getShortName() {
+        return 'jpaPersistence'
+    }
+
+    @Override
+    protected String getDefaultDatabaseProductName() {
+        return 'JPA Persistence'
+    }
+
+    @Override
+    protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() {
+        DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager()
+
+        String path = getHibernateConnection().getPath()
+        if (path == null) {
+            throw new IllegalStateException('Hibernate connection path is null')
+        }
+
+        internalPersistenceUnitManager.setPersistenceXmlLocation(path)
+
+        internalPersistenceUnitManager.preparePersistenceUnitInfos()
+        PersistenceUnitInfo persistenceUnitInfo = internalPersistenceUnitManager.obtainDefaultPersistenceUnitInfo()
+        if (persistenceUnitInfo == null) {
+            throw new IllegalStateException('No persistence unit info found for path: ' + path)
+        }
+
+        Map<String, Object> integration =
+                (Map<String, Object>) Map.of(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString())
+        return (EntityManagerFactoryBuilderImpl) Bootstrap.getEntityManagerFactoryBuilder(persistenceUnitInfo, integration)
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java
deleted file mode 100644
index 510b0a8fa0..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import java.util.Map;
-import java.util.Optional;
-
-import jakarta.persistence.spi.PersistenceUnitInfo;
-
-import liquibase.database.DatabaseConnection;
-import liquibase.ext.hibernate.database.connection.HibernateDriver;
-import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
-import org.hibernate.jpa.boot.spi.Bootstrap;
-
-import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
-
-/**
- * Database implementation for JPA configurations.
- * This supports passing a JPA persistence XML file reference.
- */
-public class JpaPersistenceDatabase extends HibernateEjb3Database {
-
-    @Override
-    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) {
-        return Optional.ofNullable(conn.getURL())
-                .map(url -> url.startsWith("jpa:persistence:"))
-                .orElse(false);
-    }
-
-    @Override
-    public String getDefaultDriver(String url) {
-        if (url != null && url.startsWith("jpa:persistence:")) {
-            return HibernateDriver.class.getName();
-        }
-        return null;
-    }
-
-    @Override
-    public String getShortName() {
-        return "jpaPersistence";
-    }
-
-    @Override
-    protected String getDefaultDatabaseProductName() {
-        return "JPA Persistence";
-    }
-
-    @Override
-    protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() {
-        DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager();
-
-        String path = Optional.ofNullable(getHibernateConnection().getPath())
-                .orElseThrow(() -> new IllegalStateException("Hibernate connection path is null"));
-
-        internalPersistenceUnitManager.setPersistenceXmlLocation(path);
-
-        internalPersistenceUnitManager.preparePersistenceUnitInfos();
-        PersistenceUnitInfo persistenceUnitInfo = Optional.of(internalPersistenceUnitManager.obtainDefaultPersistenceUnitInfo())
-                .orElseThrow(() -> new IllegalStateException("No persistence unit info found for path: " + path));
-
-        return (EntityManagerFactoryBuilderImpl) Bootstrap.getEntityManagerFactoryBuilder(
-                persistenceUnitInfo,
-                Map.of(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString()));
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.groovy
new file mode 100644
index 0000000000..1862024386
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.groovy
@@ -0,0 +1,74 @@
+package liquibase.ext.hibernate.database
+
+import java.sql.Connection
+import java.sql.SQLException
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider
+
+/**
+ * Used by hibernate to ensure no database access is performed.
+ */
+@CompileStatic
+@PackageScope
+class NoOpConnectionProvider implements ConnectionProvider {
+
+    // Fix: Classes implementing Serializable should set a serialVersionUID (PMD #12)
+    @Serial
+    private static final long serialVersionUID = 1L
+
+    @Override
+    Connection getConnection() throws SQLException {
+        throw new SQLException('No connection')
+    }
+
+    @Override
+    void closeConnection(Connection conn) {
+        // No-op
+    }
+
+    @Override
+    boolean supportsAggressiveRelease() {
+        return false
+    }
+
+    @Override
+    boolean isUnwrappableAs(Class<?> unwrapType) {
+        return false
+    }
+
+    @Override
+    def <T> T unwrap(Class<T> unwrapType) {
+        return null
+    }
+
+    /**
+     * Helper for multi-tenant or legacy calls.
+     */
+    Connection getConnection(String tenantIdentifier) throws SQLException {
+        return getConnection()
+    }
+
+    /**
+     * Helper for Hibernate 5/6 SPI calls.
+     */
+    Connection getConnection(Object o) throws SQLException {
+        return getConnection()
+    }
+
+    /**
+     * No-op release.
+     */
+    void releaseConnection(Object tenantIdentifier, Connection connection) {
+        // No-op
+    }
+
+    /**
+     * No-op release.
+     */
+    void releaseConnection(String tenantIdentifier, Connection connection) {
+        // No-op
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.java
deleted file mode 100644
index 5b5295aefe..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpConnectionProvider.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import java.io.Serial;
-import java.sql.Connection;
-import java.sql.SQLException;
-
-import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
-
-/**
- * Used by hibernate to ensure no database access is performed.
- */
-class NoOpConnectionProvider implements ConnectionProvider {
-
-    // Fix: Classes implementing Serializable should set a serialVersionUID (PMD #12)
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    @Override
-    public Connection getConnection() throws SQLException {
-        throw new SQLException("No connection");
-    }
-
-    @Override
-    public void closeConnection(Connection conn) {
-        // No-op
-    }
-
-    @Override
-    public boolean supportsAggressiveRelease() {
-        return false;
-    }
-
-    @Override
-    public boolean isUnwrappableAs(Class<?> unwrapType) {
-        return false;
-    }
-
-    @Override
-    public <T> T unwrap(Class<T> unwrapType) {
-        return null;
-    }
-
-    /**
-     * Helper for multi-tenant or legacy calls.
-     */
-    public Connection getConnection(String tenantIdentifier) throws SQLException {
-        return getConnection();
-    }
-
-    /**
-     * Helper for Hibernate 5/6 SPI calls.
-     */
-    public Connection getConnection(Object o) throws SQLException {
-        return getConnection();
-    }
-
-    /**
-     * No-op release.
-     */
-    public void releaseConnection(Object tenantIdentifier, Connection connection) {
-        // No-op
-    }
-
-    /**
-     * No-op release.
-     */
-    public void releaseConnection(String tenantIdentifier, Connection connection) {
-        // No-op
-    }
-}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.groovy b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.groovy
new file mode 100644
index 0000000000..c671b8fa62
--- /dev/null
+++ b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.groovy
@@ -0,0 +1,66 @@
+package liquibase.ext.hibernate.database
+
+import java.sql.Connection
+import java.sql.SQLException
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider
+
+/**
+ * Used by hibernate to ensure no database access is performed.
+ */
+@CompileStatic
+@PackageScope
+class NoOpMultiTenantConnectionProvider implements MultiTenantConnectionProvider {
+
+    // Fix: Classes implementing Serializable should set a serialVersionUID (PMD #13)
+    @Serial
+    private static final long serialVersionUID = 1L
+
+    @Override
+    boolean isUnwrappableAs(Class<?> unwrapType) {
+        return false
+    }
+
+    @Override
+    def <T> T unwrap(Class<T> unwrapType) {
+        return null
+    }
+
+    @Override
+    Connection getAnyConnection() {
+        return null
+    }
+
+    @Override
+    void releaseAnyConnection(Connection connection) {
+        // No-op
+    }
+
+    Connection getConnection(String tenantIdentifier) throws SQLException {
+        return null
+    }
+
+    void releaseConnection(String tenantIdentifier, Connection connection) {
+        // No-op
+    }
+
+    @Override
+    Connection getConnection(Object tenantIdentifier) throws SQLException {
+        // Fix: Added missing @Override annotation (PMD #14)
+        return null
+    }
+
+    @Override
+    void releaseConnection(Object tenantIdentifier, Connection connection) {
+        // Fix: Added missing @Override annotation (PMD #15)
+        // No-op
+    }
+
+    @Override
+    boolean supportsAggressiveRelease() {
+        return false
+    }
+
+}
diff --git a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java b/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java
deleted file mode 100644
index 6341f5dffe..0000000000
--- a/grails-data-hibernate7/dbmigration-core/src/main/groovy/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package liquibase.ext.hibernate.database;
-
-import java.io.Serial;
-import java.sql.Connection;
-import java.sql.SQLException;
-
-import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
-
-/**
- * Used by hibernate to ensure no database access is performed.
- */
-class NoOpMultiTenantConnectionProvider implements MultiTenantConnectionProvider {
-
-    // Fix: Classes implementing Serializable should set a serialVersionUID (PMD #13)
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    @Override
-    public boolean isUnwrappableAs(Class<?> unwrapType) {
-        return false;
-    }
-
-    @Override
-    public <T> T unwrap(Class<T> unwrapType) {
-        return null;
-    }
-
-    @Override
-    public Connection getAnyConnection() {
-        return null;
-    }
-
-    @Override
-    public void releaseAnyConnection(Connection connection) {
-        // No-op
-    }
-
-    public Connection getConnection(String tenantIdentifier) throws SQLException {
-        return null;
-    }
-
-    public void releaseConnection(String tenantIdentifier, Connection connection) {
-        // No-op
-    }
-
-    @Override
-    public Connection getConnection(Object tenantIdentifier) throws SQLException {
-        // Fix: Added missing @Override annotation (PMD #14)
-        return null;
-    }
-
-    @Override
-    public void releaseConnection(Object tenantIdentifier, Connection connection) {
-        // Fix: Added missing @Override annotation (PMD #15)
-        // No-op
-    }
-
-    @Override
-    public boolean supportsAggressiveRelease() {
-        return false;
-    }
-}


(grails-core) 10/14: Convert grails-data-hibernate7-spring-orm batch 4 to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 0c2e2c2f33a8db9b07b55ec7d0dbddfd29ba6872
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 21:38:15 2026 -0500

    Convert grails-data-hibernate7-spring-orm batch 4 to Groovy
    
    Converts HibernateTransactionManager and HibernateTemplate to @CompileStatic
    Groovy, completing the spring-orm module (no Java sources remain). Upstream
    Spring copyright headers are preserved verbatim.
    
    Groovy-specific adjustments: Session/SessionImplementor.getTransaction() is
    invoked through a SharedSessionContract cast so static type checking resolves
    Hibernate's covariant Transaction return type instead of EntityManager's
    EntityTransaction; the ConnectionHandle lambda and the HibernateCallback
    lambdas become explicitly-coerced closures; the close-suppressing proxy
    handler uses a classic switch in place of the Java switch expression; the
    SuspendedResourcesHolder accessors are @PackageScope so the enclosing class
    can reach them.
    
    Verified with codenarcMain/checkstyleMain on spring-orm and the full
    grails-data-hibernate7-core, grails-data-hibernate7 and
    grails-data-hibernate7-spring-boot test suites.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 ...rnateTemplate.java => HibernateTemplate.groovy} | 856 ++++++++++-----------
 ...ger.java => HibernateTransactionManager.groovy} | 667 ++++++++--------
 2 files changed, 731 insertions(+), 792 deletions(-)

diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTemplate.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTemplate.groovy
similarity index 50%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTemplate.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTemplate.groovy
index 23b0bc4574..87cd769529 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTemplate.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTemplate.groovy
@@ -13,39 +13,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.grails.orm.hibernate.support.hibernate7;
-
-import java.io.Serializable;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Collection;
-import java.util.List;
-
-import jakarta.persistence.PersistenceException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.Filter;
-import org.hibernate.FlushMode;
-import org.hibernate.Hibernate;
-import org.hibernate.HibernateException;
-import org.hibernate.LockMode;
-import org.hibernate.LockOptions;
-import org.hibernate.ReplicationMode;
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.hibernate.query.Query;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.dao.DataAccessException;
-import org.springframework.dao.InvalidDataAccessApiUsageException;
-import org.springframework.transaction.support.ResourceHolderSupport;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-import org.springframework.util.Assert;
+package org.grails.orm.hibernate.support.hibernate7
+
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+
+import groovy.transform.CompileStatic
+import jakarta.persistence.PersistenceException
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.hibernate.Filter
+import org.hibernate.FlushMode
+import org.hibernate.Hibernate
+import org.hibernate.HibernateException
+import org.hibernate.LockMode
+import org.hibernate.LockOptions
+import org.hibernate.ReplicationMode
+import org.hibernate.Session
+import org.hibernate.SessionFactory
+import org.hibernate.engine.spi.SessionImplementor
+import org.hibernate.query.Query
+import org.springframework.beans.factory.InitializingBean
+import org.springframework.dao.DataAccessException
+import org.springframework.dao.InvalidDataAccessApiUsageException
+import org.springframework.transaction.support.ResourceHolderSupport
+import org.springframework.transaction.support.TransactionSynchronizationManager
+import org.springframework.util.Assert
 
 /**
  * Helper class that simplifies Hibernate data access code. Automatically
@@ -84,59 +79,56 @@ import org.springframework.util.Assert;
  * @see org.springframework.orm.hibernate7.support.OpenSessionInViewFilter
  * @see org.springframework.orm.hibernate7.support.OpenSessionInViewInterceptor
  */
-public class HibernateTemplate implements HibernateOperations, InitializingBean {
+@CompileStatic
+class HibernateTemplate implements HibernateOperations, InitializingBean {
 
-    protected final Log logger = LogFactory.getLog(getClass());
+    protected final Log logger = LogFactory.getLog(getClass())
 
-    @Nullable
-    private SessionFactory sessionFactory;
+    private SessionFactory sessionFactory
 
-    @Nullable
-    private String[] filterNames;
+    private String[] filterNames
 
-    private boolean exposeNativeSession = false;
+    private boolean exposeNativeSession = false
 
-    private boolean checkWriteOperations = true;
+    private boolean checkWriteOperations = true
 
-    private boolean cacheQueries = false;
+    private boolean cacheQueries = false
 
-    @Nullable
-    private String queryCacheRegion;
+    private String queryCacheRegion
 
-    private int fetchSize = 0;
+    private int fetchSize = 0
 
-    private int maxResults = 0;
+    private int maxResults = 0
 
     /**
      * Create a new HibernateTemplate instance.
      */
-    public HibernateTemplate() {
+    HibernateTemplate() {
     }
 
     /**
      * Create a new HibernateTemplate instance.
      * @param sessionFactory the SessionFactory to create Sessions with
      */
-    public HibernateTemplate(SessionFactory sessionFactory) {
-        setSessionFactory(sessionFactory);
-        afterPropertiesSet();
+    HibernateTemplate(SessionFactory sessionFactory) {
+        setSessionFactory(sessionFactory)
+        afterPropertiesSet()
     }
 
     /**
      * Set the Hibernate SessionFactory that should be used to create
      * Hibernate Sessions.
      */
-    public void setSessionFactory(@Nullable SessionFactory sessionFactory) {
-        this.sessionFactory = sessionFactory;
+    void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory
     }
 
     /**
      * Return the Hibernate SessionFactory that should be used to create
      * Hibernate Sessions.
      */
-    @Nullable
-    public SessionFactory getSessionFactory() {
-        return this.sessionFactory;
+    SessionFactory getSessionFactory() {
+        return this.sessionFactory
     }
 
     /**
@@ -146,9 +138,9 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @since 5.0
      */
     protected final SessionFactory obtainSessionFactory() {
-        SessionFactory sessionFactory = getSessionFactory();
-        Assert.state(sessionFactory != null, "No SessionFactory set");
-        return sessionFactory;
+        SessionFactory sessionFactory = getSessionFactory()
+        Assert.state(sessionFactory != null, 'No SessionFactory set')
+        return sessionFactory
     }
 
     /**
@@ -161,16 +153,15 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @see #enableFilters(Session)
      * @see Session#enableFilter(String)
      */
-    public void setFilterNames(@Nullable String... filterNames) {
-        this.filterNames = filterNames;
+    void setFilterNames(String... filterNames) {
+        this.filterNames = filterNames
     }
 
     /**
      * Return the names of Hibernate filters to be activated, if any.
      */
-    @Nullable
-    public String[] getFilterNames() {
-        return this.filterNames;
+    String[] getFilterNames() {
+        return this.filterNames
     }
 
     /**
@@ -186,16 +177,16 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @see #prepareQuery
      * @see #prepareCriteria
      */
-    public void setExposeNativeSession(boolean exposeNativeSession) {
-        this.exposeNativeSession = exposeNativeSession;
+    void setExposeNativeSession(boolean exposeNativeSession) {
+        this.exposeNativeSession = exposeNativeSession
     }
 
     /**
      * Return whether to expose the native Hibernate Session to
      * HibernateCallback code, or rather a Session proxy.
      */
-    public boolean isExposeNativeSession() {
-        return this.exposeNativeSession;
+    boolean isExposeNativeSession() {
+        return this.exposeNativeSession
     }
 
     /**
@@ -207,16 +198,16 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @see #checkWriteOperationAllowed
      * @see org.springframework.transaction.TransactionDefinition#isReadOnly
      */
-    public void setCheckWriteOperations(boolean checkWriteOperations) {
-        this.checkWriteOperations = checkWriteOperations;
+    void setCheckWriteOperations(boolean checkWriteOperations) {
+        this.checkWriteOperations = checkWriteOperations
     }
 
     /**
      * Return whether to check that the Hibernate Session is not in read-only
      * mode in case of write operations (save/update/delete).
      */
-    public boolean isCheckWriteOperations() {
-        return this.checkWriteOperations;
+    boolean isCheckWriteOperations() {
+        return this.checkWriteOperations
     }
 
     /**
@@ -230,15 +221,15 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @see Query#setCacheable
      * @see Criteria#setCacheable
      */
-    public void setCacheQueries(boolean cacheQueries) {
-        this.cacheQueries = cacheQueries;
+    void setCacheQueries(boolean cacheQueries) {
+        this.cacheQueries = cacheQueries
     }
 
     /**
      * Return whether to cache all queries executed by this template.
      */
-    public boolean isCacheQueries() {
-        return this.cacheQueries;
+    boolean isCacheQueries() {
+        return this.cacheQueries
     }
 
     /**
@@ -251,16 +242,15 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @see Query#setCacheRegion
      * @see Criteria#setCacheRegion
      */
-    public void setQueryCacheRegion(@Nullable String queryCacheRegion) {
-        this.queryCacheRegion = queryCacheRegion;
+    void setQueryCacheRegion(String queryCacheRegion) {
+        this.queryCacheRegion = queryCacheRegion
     }
 
     /**
      * Return the name of the cache region for queries executed by this template.
      */
-    @Nullable
-    public String getQueryCacheRegion() {
-        return this.queryCacheRegion;
+    String getQueryCacheRegion() {
+        return this.queryCacheRegion
     }
 
     /**
@@ -270,15 +260,15 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * avoid transferring row data that will never be read by the application.
      * <p>Default is 0, indicating to use the JDBC driver's default.
      */
-    public void setFetchSize(int fetchSize) {
-        this.fetchSize = fetchSize;
+    void setFetchSize(int fetchSize) {
+        this.fetchSize = fetchSize
     }
 
     /**
      * Return the fetch size specified for this HibernateTemplate.
      */
-    public int getFetchSize() {
-        return this.fetchSize;
+    int getFetchSize() {
+        return this.fetchSize
     }
 
     /**
@@ -289,28 +279,27 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * when performing searches that might return a large number of matches).
      * <p>Default is 0, indicating to use the JDBC driver's default.
      */
-    public void setMaxResults(int maxResults) {
-        this.maxResults = maxResults;
+    void setMaxResults(int maxResults) {
+        this.maxResults = maxResults
     }
 
     /**
      * Return the maximum number of rows specified for this HibernateTemplate.
      */
-    public int getMaxResults() {
-        return this.maxResults;
+    int getMaxResults() {
+        return this.maxResults
     }
 
     @Override
-    public void afterPropertiesSet() {
+    void afterPropertiesSet() {
         if (getSessionFactory() == null) {
-            throw new IllegalArgumentException("Property 'sessionFactory' is required");
+            throw new IllegalArgumentException("Property 'sessionFactory' is required")
         }
     }
 
     @Override
-    @Nullable
-    public <T> T execute(HibernateCallback<T> action) throws DataAccessException {
-        return doExecute(action, false);
+    def <T> T execute(HibernateCallback<T> action) throws DataAccessException {
+        return doExecute(action, false)
     }
 
     /**
@@ -322,9 +311,8 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @return a result object returned by the action, or {@code null}
      * @throws DataAccessException in case of Hibernate errors
      */
-    @Nullable
-    public <T> T executeWithNativeSession(HibernateCallback<T> action) {
-        return doExecute(action, true);
+    def <T> T executeWithNativeSession(HibernateCallback<T> action) {
+        return doExecute(action, true)
     }
 
     /**
@@ -335,49 +323,42 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @return a result object returned by the action, or {@code null}
      * @throws DataAccessException in case of Hibernate errors
      */
-    @Nullable
     protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession) throws DataAccessException {
-        Assert.notNull(action, "Callback object must not be null");
+        Assert.notNull(action, 'Callback object must not be null')
 
-        Session session = null;
-        boolean isNew = false;
+        Session session = null
+        boolean isNew = false
         try {
-            session = obtainSessionFactory().getCurrentSession();
-        }
-        catch (HibernateException ex) {
-            logger.debug("Could not retrieve pre-bound Hibernate session", ex);
+            session = obtainSessionFactory().getCurrentSession()
+        } catch (HibernateException ex) {
+            logger.debug('Could not retrieve pre-bound Hibernate session', ex)
         }
         if (session == null) {
-            session = obtainSessionFactory().openSession();
-            session.setHibernateFlushMode(FlushMode.MANUAL);
-            isNew = true;
+            session = obtainSessionFactory().openSession()
+            session.setHibernateFlushMode(FlushMode.MANUAL)
+            isNew = true
         }
 
         try {
-            enableFilters(session);
+            enableFilters(session)
             Session sessionToExpose =
-                    (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
-            return action.doInHibernate(sessionToExpose);
-        }
-        catch (HibernateException ex) {
-            throw SessionFactoryUtils.convertHibernateAccessException(ex);
-        }
-        catch (PersistenceException ex) {
-            if (ex.getCause() instanceof HibernateException hibernateEx) {
-                throw SessionFactoryUtils.convertHibernateAccessException(hibernateEx);
+                    (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session))
+            return action.doInHibernate(sessionToExpose)
+        } catch (HibernateException ex) {
+            throw SessionFactoryUtils.convertHibernateAccessException(ex)
+        } catch (PersistenceException ex) {
+            if (ex.getCause() instanceof HibernateException) {
+                throw SessionFactoryUtils.convertHibernateAccessException((HibernateException) ex.getCause())
             }
-            throw ex;
-        }
-        catch (RuntimeException ex) {
+            throw ex
+        } catch (RuntimeException ex) {
             // Callback code threw application exception...
-            throw ex;
-        }
-        finally {
+            throw ex
+        } finally {
             if (isNew) {
-                SessionFactoryUtils.closeSession(session);
-            }
-            else {
-                disableFilters(session);
+                SessionFactoryUtils.closeSession(session)
+            } else {
+                disableFilters(session)
             }
         }
     }
@@ -393,8 +374,8 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      */
     protected Session createSessionProxy(Session session) {
         return (Session) Proxy.newProxyInstance(
-                session.getClass().getClassLoader(), new Class<?>[] {Session.class},
-                new CloseSuppressingInvocationHandler(session));
+                session.getClass().getClassLoader(), [Session] as Class<?>[],
+                new CloseSuppressingInvocationHandler(session))
     }
 
     /**
@@ -404,10 +385,10 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @see Session#enableFilter(String)
      */
     protected void enableFilters(Session session) {
-        String[] filterNames = getFilterNames();
+        String[] filterNames = getFilterNames()
         if (filterNames != null) {
             for (String filterName : filterNames) {
-                session.enableFilter(filterName);
+                session.enableFilter(filterName)
             }
         }
     }
@@ -419,532 +400,516 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      * @see Session#disableFilter(String)
      */
     protected void disableFilters(Session session) {
-        String[] filterNames = getFilterNames();
+        String[] filterNames = getFilterNames()
         if (filterNames != null) {
             for (String filterName : filterNames) {
-                session.disableFilter(filterName);
+                session.disableFilter(filterName)
             }
         }
     }
 
-
     //-------------------------------------------------------------------------
     // Convenience methods for loading individual objects
     //-------------------------------------------------------------------------
 
     @Override
-    @Nullable
-    public <T> T get(Class<T> entityClass, Serializable id) throws DataAccessException {
-        return get(entityClass, id, null);
+    def <T> T get(Class<T> entityClass, Serializable id) throws DataAccessException {
+        return get(entityClass, id, null)
     }
 
     @Override
-    @Nullable
-    public <T> T get(Class<T> entityClass, Serializable id, @Nullable LockMode lockMode) throws DataAccessException {
-        return executeWithNativeSession(session -> {
+    def <T> T get(Class<T> entityClass, Serializable id, LockMode lockMode) throws DataAccessException {
+        return executeWithNativeSession({ Session session ->
             if (lockMode != null) {
-                return session.get(entityClass, id, new LockOptions(lockMode));
-            }
-            else {
-                return session.get(entityClass, id);
+                return session.get(entityClass, id, new LockOptions(lockMode))
+            } else {
+                return session.get(entityClass, id)
             }
-        });
+        } as HibernateCallback<T>)
     }
 
     @Override
-    @Nullable
-    public Object get(String entityName, Serializable id) throws DataAccessException {
-        return get(entityName, id, null);
+    Object get(String entityName, Serializable id) throws DataAccessException {
+        return get(entityName, id, null)
     }
 
     @Override
-    @Nullable
-    public Object get(String entityName, Serializable id, @Nullable LockMode lockMode) throws DataAccessException {
-        return executeWithNativeSession(session -> {
+    Object get(String entityName, Serializable id, LockMode lockMode) throws DataAccessException {
+        return executeWithNativeSession({ Session session ->
             if (lockMode != null) {
-                return session.get(entityName, id, new LockOptions(lockMode));
-            }
-            else {
-                return session.get(entityName, id);
+                return session.get(entityName, id, new LockOptions(lockMode))
+            } else {
+                return session.get(entityName, id)
             }
-        });
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public <T> T load(Class<T> entityClass, Serializable id) throws DataAccessException {
-        return load(entityClass, id, null);
+    def <T> T load(Class<T> entityClass, Serializable id) throws DataAccessException {
+        return load(entityClass, id, null)
     }
 
     @Override
-    public <T> T load(Class<T> entityClass, Serializable id, @Nullable LockMode lockMode)
+    def <T> T load(Class<T> entityClass, Serializable id, LockMode lockMode)
             throws DataAccessException {
 
-        return nonNull(executeWithNativeSession(session -> {
+        return nonNull(executeWithNativeSession({ Session session ->
             if (lockMode != null) {
-                return session.get(entityClass, id, new LockOptions(lockMode));
-            }
-            else {
-                return session.getReference(entityClass, id);
+                return session.get(entityClass, id, new LockOptions(lockMode))
+            } else {
+                return session.getReference(entityClass, id)
             }
-        }));
+        } as HibernateCallback<T>))
     }
 
     @Override
-    public Object load(String entityName, Serializable id) throws DataAccessException {
-        return load(entityName, id, null);
+    Object load(String entityName, Serializable id) throws DataAccessException {
+        return load(entityName, id, null)
     }
 
     @Override
-    public Object load(String entityName, Serializable id, @Nullable LockMode lockMode) throws DataAccessException {
-        return nonNull(executeWithNativeSession(session -> {
+    Object load(String entityName, Serializable id, LockMode lockMode) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
             if (lockMode != null) {
-                return session.get(entityName, id, new LockOptions(lockMode));
-            }
-            else {
-                return session.getReference(entityName, id);
+                return session.get(entityName, id, new LockOptions(lockMode))
+            } else {
+                return session.getReference(entityName, id)
             }
-        }));
+        } as HibernateCallback<Object>))
     }
 
     @Override
-    public void load(Object entity, Serializable id) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            session.getIdentifier(entity); // Check if session knows about it?
+    void load(Object entity, Serializable id) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            session.getIdentifier(entity) // Check if session knows about it?
             // Actually, load(entity, id) was used to refresh an existing object from the DB.
             // In Hibernate 7, you'd use get or find.
-            return null;
-        });
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void refresh(Object entity) throws DataAccessException {
-        refresh(entity, null);
+    void refresh(Object entity) throws DataAccessException {
+        refresh(entity, null)
     }
 
     @Override
-    public void refresh(Object entity, @Nullable LockMode lockMode) throws DataAccessException {
-        executeWithNativeSession(session -> {
+    void refresh(Object entity, LockMode lockMode) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
             if (lockMode != null) {
-                session.refresh(entity, new LockOptions(lockMode));
-            }
-            else {
-                session.refresh(entity);
+                session.refresh(entity, new LockOptions(lockMode))
+            } else {
+                session.refresh(entity)
             }
-            return null;
-        });
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public boolean contains(Object entity) throws DataAccessException {
-        Boolean result = executeWithNativeSession(session -> session.contains(entity));
-        Assert.state(result != null, "No contains result");
-        return result;
+    boolean contains(Object entity) throws DataAccessException {
+        Boolean result = executeWithNativeSession({ Session session -> session.contains(entity) } as HibernateCallback<Boolean>)
+        Assert.state(result != null, 'No contains result')
+        return result
     }
 
     @Override
-    public void evict(Object entity) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            session.evict(entity);
-            return null;
-        });
+    void evict(Object entity) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            session.evict(entity)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void initialize(Object proxy) throws DataAccessException {
+    void initialize(Object proxy) throws DataAccessException {
         try {
-            Hibernate.initialize(proxy);
-        }
-        catch (HibernateException ex) {
-            throw SessionFactoryUtils.convertHibernateAccessException(ex);
+            Hibernate.initialize(proxy)
+        } catch (HibernateException ex) {
+            throw SessionFactoryUtils.convertHibernateAccessException(ex)
         }
     }
 
     @Override
-    public Filter enableFilter(String filterName) throws IllegalStateException {
-        Session session = obtainSessionFactory().getCurrentSession();
-        Filter filter = session.getEnabledFilter(filterName);
+    Filter enableFilter(String filterName) throws IllegalStateException {
+        Session session = obtainSessionFactory().getCurrentSession()
+        Filter filter = session.getEnabledFilter(filterName)
         if (filter == null) {
-            filter = session.enableFilter(filterName);
+            filter = session.enableFilter(filterName)
         }
-        return filter;
+        return filter
     }
 
-
     //-------------------------------------------------------------------------
     // Convenience methods for storing individual objects
     //-------------------------------------------------------------------------
 
     @Override
-    public void lock(Object entity, LockMode lockMode) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            session.lock(entity, lockMode);
-            return null;
-        });
+    void lock(Object entity, LockMode lockMode) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            session.lock(entity, lockMode)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void lock(String entityName, Object entity, LockMode lockMode)
+    void lock(String entityName, Object entity, LockMode lockMode)
             throws DataAccessException {
 
-        executeWithNativeSession(session -> {
-            session.lock(entity, lockMode);
-            return null;
-        });
+        executeWithNativeSession({ Session session ->
+            session.lock(entity, lockMode)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public Serializable save(Object entity) throws DataAccessException {
-        return nonNull(executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            org.hibernate.engine.spi.SessionImplementor sessionImpl = (org.hibernate.engine.spi.SessionImplementor) session;
-            Boolean isUnsaved = sessionImpl.getEntityPersister(null, entity).isTransient(entity, sessionImpl);
+    Serializable save(Object entity) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            SessionImplementor sessionImpl = (SessionImplementor) session
+            Boolean isUnsaved = sessionImpl.getEntityPersister(null, entity).isTransient(entity, sessionImpl)
             if (Boolean.TRUE.equals(isUnsaved)) {
-                session.persist(entity);
+                session.persist(entity)
             } else {
-                session.merge(entity);
+                session.merge(entity)
             }
-            return (Serializable) session.getIdentifier(entity);
-        }));
+            return (Serializable) session.getIdentifier(entity)
+        } as HibernateCallback<Serializable>))
     }
 
     @Override
-    public Serializable save(String entityName, Object entity) throws DataAccessException {
-        return nonNull(executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            org.hibernate.engine.spi.SessionImplementor sessionImpl = (org.hibernate.engine.spi.SessionImplementor) session;
-            Boolean isUnsaved = sessionImpl.getEntityPersister(entityName, entity).isTransient(entity, sessionImpl);
+    Serializable save(String entityName, Object entity) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            SessionImplementor sessionImpl = (SessionImplementor) session
+            Boolean isUnsaved = sessionImpl.getEntityPersister(entityName, entity).isTransient(entity, sessionImpl)
             if (Boolean.TRUE.equals(isUnsaved)) {
-                session.persist(entityName, entity);
+                session.persist(entityName, entity)
             } else {
-                session.merge(entityName, entity);
+                session.merge(entityName, entity)
             }
-            return (Serializable) session.getIdentifier(entity);
-        }));
+            return (Serializable) session.getIdentifier(entity)
+        } as HibernateCallback<Serializable>))
     }
 
     @Override
-    public void update(Object entity) throws DataAccessException {
-        update(entity, null);
+    void update(Object entity) throws DataAccessException {
+        update(entity, null)
     }
 
     @Override
-    public void update(Object entity, @Nullable LockMode lockMode) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            session.merge(entity);
+    void update(Object entity, LockMode lockMode) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            session.merge(entity)
             if (lockMode != null) {
-                session.lock(entity, lockMode);
+                session.lock(entity, lockMode)
             }
-            return null;
-        });
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void update(String entityName, Object entity) throws DataAccessException {
-        update(entityName, entity, null);
+    void update(String entityName, Object entity) throws DataAccessException {
+        update(entityName, entity, null)
     }
 
     @Override
-    public void update(String entityName, Object entity, @Nullable LockMode lockMode)
+    void update(String entityName, Object entity, LockMode lockMode)
             throws DataAccessException {
 
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            session.merge(entityName, entity);
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            session.merge(entityName, entity)
             if (lockMode != null) {
-                session.lock(entity, lockMode);
+                session.lock(entity, lockMode)
             }
-            return null;
-        });
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void saveOrUpdate(Object entity) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            org.hibernate.engine.spi.SessionImplementor sessionImpl = (org.hibernate.engine.spi.SessionImplementor) session;
-            Boolean isUnsaved = sessionImpl.getEntityPersister(null, entity).isTransient(entity, sessionImpl);
+    void saveOrUpdate(Object entity) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            SessionImplementor sessionImpl = (SessionImplementor) session
+            Boolean isUnsaved = sessionImpl.getEntityPersister(null, entity).isTransient(entity, sessionImpl)
             if (Boolean.TRUE.equals(isUnsaved)) {
-                session.persist(entity);
+                session.persist(entity)
             } else {
-                session.merge(entity);
+                session.merge(entity)
             }
-            return null;
-        });
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void saveOrUpdate(String entityName, Object entity) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            org.hibernate.engine.spi.SessionImplementor sessionImpl = (org.hibernate.engine.spi.SessionImplementor) session;
-            Boolean isUnsaved = sessionImpl.getEntityPersister(entityName, entity).isTransient(entity, sessionImpl);
+    void saveOrUpdate(String entityName, Object entity) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            SessionImplementor sessionImpl = (SessionImplementor) session
+            Boolean isUnsaved = sessionImpl.getEntityPersister(entityName, entity).isTransient(entity, sessionImpl)
             if (Boolean.TRUE.equals(isUnsaved)) {
-                session.persist(entityName, entity);
+                session.persist(entityName, entity)
             } else {
-                session.merge(entityName, entity);
+                session.merge(entityName, entity)
             }
-            return null;
-        });
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            session.replicate(entity, replicationMode);
-            return null;
-        });
+    void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            session.replicate(entity, replicationMode)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void replicate(String entityName, Object entity, ReplicationMode replicationMode)
+    void replicate(String entityName, Object entity, ReplicationMode replicationMode)
             throws DataAccessException {
 
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            session.replicate(entityName, entity, replicationMode);
-            return null;
-        });
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            session.replicate(entityName, entity, replicationMode)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void persist(Object entity) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            session.persist(entity);
-            return null;
-        });
+    void persist(Object entity) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            session.persist(entity)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void persist(String entityName, Object entity) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            session.persist(entityName, entity);
-            return null;
-        });
+    void persist(String entityName, Object entity) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            session.persist(entityName, entity)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    @SuppressWarnings("unchecked")
-    public <T> T merge(T entity) throws DataAccessException {
-        return nonNull(executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            return (T) session.merge(entity);
-        }));
+    @SuppressWarnings('unchecked')
+    def <T> T merge(T entity) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            return (T) session.merge(entity)
+        } as HibernateCallback<T>))
     }
 
     @Override
-    @SuppressWarnings("unchecked")
-    public <T> T merge(String entityName, T entity) throws DataAccessException {
-        return nonNull(executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
-            return (T) session.merge(entityName, entity);
-        }));
+    @SuppressWarnings('unchecked')
+    def <T> T merge(String entityName, T entity) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
+            return (T) session.merge(entityName, entity)
+        } as HibernateCallback<T>))
     }
 
     @Override
-    public void delete(Object entity) throws DataAccessException {
-        delete(entity, null);
+    void delete(Object entity) throws DataAccessException {
+        delete(entity, null)
     }
 
     @Override
-    public void delete(Object entity, @Nullable LockMode lockMode) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
+    void delete(Object entity, LockMode lockMode) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
             if (lockMode != null) {
-                session.lock(entity, lockMode);
+                session.lock(entity, lockMode)
             }
-            session.remove(entity);
-            return null;
-        });
+            session.remove(entity)
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void delete(String entityName, Object entity) throws DataAccessException {
-        delete(entityName, entity, null);
+    void delete(String entityName, Object entity) throws DataAccessException {
+        delete(entityName, entity, null)
     }
 
     @Override
-    public void delete(String entityName, Object entity, @Nullable LockMode lockMode)
+    void delete(String entityName, Object entity, LockMode lockMode)
             throws DataAccessException {
 
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
             if (lockMode != null) {
-                session.lock(entity, lockMode);
+                session.lock(entity, lockMode)
             }
-            session.remove(entity); // entityName not supported directly in remove, but session.remove(entity) works
-            return null;
-        });
+            session.remove(entity) // entityName not supported directly in remove, but session.remove(entity) works
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void deleteAll(Collection<?> entities) throws DataAccessException {
-        executeWithNativeSession(session -> {
-            checkWriteOperationAllowed(session);
+    void deleteAll(Collection<?> entities) throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            checkWriteOperationAllowed(session)
             for (Object entity : entities) {
-                session.remove(entity);
+                session.remove(entity)
             }
-            return null;
-        });
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void flush() throws DataAccessException {
-        executeWithNativeSession(session -> {
-            session.flush();
-            return null;
-        });
+    void flush() throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            session.flush()
+            return null
+        } as HibernateCallback<Object>)
     }
 
     @Override
-    public void clear() throws DataAccessException {
-        executeWithNativeSession(session -> {
-            session.clear();
-            return null;
-        });
+    void clear() throws DataAccessException {
+        executeWithNativeSession({ Session session ->
+            session.clear()
+            return null
+        } as HibernateCallback<Object>)
     }
 
-
     //-------------------------------------------------------------------------
     // Convenience finder methods for HQL strings
     //-------------------------------------------------------------------------
 
     @Deprecated
     @Override
-    public List<?> find(String queryString, @Nullable Object... values) throws DataAccessException {
-        return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> {
-            Query<?> queryObject = session.createQuery(queryString);
-            prepareQuery(queryObject);
+    List<?> find(String queryString, Object... values) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            Query<?> queryObject = session.createQuery(queryString)
+            prepareQuery(queryObject)
             if (values != null) {
                 for (int i = 0; i < values.length; i++) {
-                    queryObject.setParameter(i, values[i]);
+                    queryObject.setParameter(i, values[i])
                 }
             }
-            return queryObject.list();
-        }));
+            return queryObject.list()
+        } as HibernateCallback<List<?>>))
     }
 
     @Deprecated
     @Override
-    public List<?> findByNamedParam(String queryString, String paramName, Object value)
+    List<?> findByNamedParam(String queryString, String paramName, Object value)
             throws DataAccessException {
 
-        return findByNamedParam(queryString, new String[] {paramName}, new Object[] {value});
+        return findByNamedParam(queryString, [paramName] as String[], [value] as Object[])
     }
 
     @Deprecated
     @Override
-    public List<?> findByNamedParam(String queryString, String[] paramNames, Object[] values)
+    List<?> findByNamedParam(String queryString, String[] paramNames, Object[] values)
             throws DataAccessException {
 
         if (paramNames.length != values.length) {
-            throw new IllegalArgumentException("Length of paramNames array must match length of values array");
+            throw new IllegalArgumentException('Length of paramNames array must match length of values array')
         }
-        return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> {
-            Query<?> queryObject = session.createQuery(queryString);
-            prepareQuery(queryObject);
+        return nonNull(executeWithNativeSession({ Session session ->
+            Query<?> queryObject = session.createQuery(queryString)
+            prepareQuery(queryObject)
             for (int i = 0; i < values.length; i++) {
-                applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
+                applyNamedParameterToQuery(queryObject, paramNames[i], values[i])
             }
-            return queryObject.list();
-        }));
+            return queryObject.list()
+        } as HibernateCallback<List<?>>))
     }
 
     @Deprecated
     @Override
-    public List<?> findByValueBean(String queryString, Object valueBean) throws DataAccessException {
-        return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> {
-            Query<?> queryObject = session.createQuery(queryString);
-            prepareQuery(queryObject);
-            queryObject.setProperties(valueBean);
-            return queryObject.list();
-        }));
+    List<?> findByValueBean(String queryString, Object valueBean) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            Query<?> queryObject = session.createQuery(queryString)
+            prepareQuery(queryObject)
+            queryObject.setProperties(valueBean)
+            return queryObject.list()
+        } as HibernateCallback<List<?>>))
     }
 
-
     //-------------------------------------------------------------------------
     // Convenience finder methods for named queries
     //-------------------------------------------------------------------------
 
     @Deprecated
     @Override
-    public List<?> findByNamedQuery(String queryName, @Nullable Object... values) throws DataAccessException {
-        return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> {
-            Query<?> queryObject = session.getNamedQuery(queryName);
-            prepareQuery(queryObject);
+    List<?> findByNamedQuery(String queryName, Object... values) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            Query<?> queryObject = session.getNamedQuery(queryName)
+            prepareQuery(queryObject)
             if (values != null) {
                 for (int i = 0; i < values.length; i++) {
-                    queryObject.setParameter(i, values[i]);
+                    queryObject.setParameter(i, values[i])
                 }
             }
-            return queryObject.list();
-        }));
+            return queryObject.list()
+        } as HibernateCallback<List<?>>))
     }
 
     @Deprecated
     @Override
-    public List<?> findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
+    List<?> findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
             throws DataAccessException {
 
-        return findByNamedQueryAndNamedParam(queryName, new String[] {paramName}, new Object[] {value});
+        return findByNamedQueryAndNamedParam(queryName, [paramName] as String[], [value] as Object[])
     }
 
     @Deprecated
     @Override
-    @SuppressWarnings("NullAway")
-    public List<?> findByNamedQueryAndNamedParam(
-            String queryName, @Nullable String[] paramNames, @Nullable Object[] values)
+    List<?> findByNamedQueryAndNamedParam(
+            String queryName, String[] paramNames, Object[] values)
             throws DataAccessException {
 
         if (values != null && (paramNames == null || paramNames.length != values.length)) {
-            throw new IllegalArgumentException("Length of paramNames array must match length of values array");
+            throw new IllegalArgumentException('Length of paramNames array must match length of values array')
         }
-        return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> {
-            Query<?> queryObject = session.getNamedQuery(queryName);
-            prepareQuery(queryObject);
+        return nonNull(executeWithNativeSession({ Session session ->
+            Query<?> queryObject = session.getNamedQuery(queryName)
+            prepareQuery(queryObject)
             if (values != null) {
                 for (int i = 0; i < values.length; i++) {
-                    applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
+                    applyNamedParameterToQuery(queryObject, paramNames[i], values[i])
                 }
             }
-            return queryObject.list();
-        }));
+            return queryObject.list()
+        } as HibernateCallback<List<?>>))
     }
 
     @Deprecated
     @Override
-    public List<?> findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException {
-        return nonNull(executeWithNativeSession((HibernateCallback<List<?>>) session -> {
-            Query<?> queryObject = session.getNamedQuery(queryName);
-            prepareQuery(queryObject);
-            queryObject.setProperties(valueBean);
-            return queryObject.list();
-        }));
+    List<?> findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException {
+        return nonNull(executeWithNativeSession({ Session session ->
+            Query<?> queryObject = session.getNamedQuery(queryName)
+            prepareQuery(queryObject)
+            queryObject.setProperties(valueBean)
+            return queryObject.list()
+        } as HibernateCallback<List<?>>))
     }
 
-
     //-------------------------------------------------------------------------
     // Convenience query methods for iteration and bulk updates/deletes
     //-------------------------------------------------------------------------
 
     @Deprecated
     @Override
-    public int bulkUpdate(String queryString, @Nullable Object... values) throws DataAccessException {
-        Integer result = executeWithNativeSession(session -> {
-            Query<?> queryObject = session.createQuery(queryString);
-            prepareQuery(queryObject);
+    int bulkUpdate(String queryString, Object... values) throws DataAccessException {
+        Integer result = executeWithNativeSession({ Session session ->
+            Query<?> queryObject = session.createQuery(queryString)
+            prepareQuery(queryObject)
             if (values != null) {
                 for (int i = 0; i < values.length; i++) {
-                    queryObject.setParameter(i, values[i]);
+                    queryObject.setParameter(i, values[i])
                 }
             }
-            return queryObject.executeUpdate();
-        });
-        Assert.state(result != null, "No update count");
-        return result;
+            return queryObject.executeUpdate()
+        } as HibernateCallback<Integer>)
+        Assert.state(result != null, 'No update count')
+        return result
     }
 
     //-------------------------------------------------------------------------
@@ -964,8 +929,8 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
     protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
         if (isCheckWriteOperations() && session.getHibernateFlushMode().lessThan(FlushMode.COMMIT)) {
             throw new InvalidDataAccessApiUsageException(
-                    "Write operations are not allowed in read-only mode (FlushMode.MANUAL): " +
-                    "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
+                    'Write operations are not allowed in read-only mode (FlushMode.MANUAL): ' +
+                    "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.")
         }
     }
 
@@ -978,22 +943,22 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      */
     protected void prepareQuery(Query<?> queryObject) {
         if (isCacheQueries()) {
-            queryObject.setCacheable(true);
+            queryObject.setCacheable(true)
             if (getQueryCacheRegion() != null) {
-                queryObject.setCacheRegion(getQueryCacheRegion());
+                queryObject.setCacheRegion(getQueryCacheRegion())
             }
         }
         if (getFetchSize() > 0) {
-            queryObject.setFetchSize(getFetchSize());
+            queryObject.setFetchSize(getFetchSize())
         }
         if (getMaxResults() > 0) {
-            queryObject.setMaxResults(getMaxResults());
+            queryObject.setMaxResults(getMaxResults())
         }
 
         ResourceHolderSupport sessionHolder =
-                (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory());
+                (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory())
         if (sessionHolder != null && sessionHolder.hasTimeout()) {
-            queryObject.setTimeout(sessionHolder.getTimeToLiveInSeconds());
+            queryObject.setTimeout(sessionHolder.getTimeToLiveInSeconds())
         }
     }
 
@@ -1007,20 +972,18 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
     protected void applyNamedParameterToQuery(Query<?> queryObject, String paramName, Object value)
             throws HibernateException {
 
-        if (value instanceof Collection<?> collection) {
-            queryObject.setParameterList(paramName, collection);
-        }
-        else if (value instanceof Object[] array) {
-            queryObject.setParameterList(paramName, array);
-        }
-        else {
-            queryObject.setParameter(paramName, value);
+        if (value instanceof Collection) {
+            queryObject.setParameterList(paramName, (Collection<?>) value)
+        } else if (value instanceof Object[]) {
+            queryObject.setParameterList(paramName, (Object[]) value)
+        } else {
+            queryObject.setParameter(paramName, value)
         }
     }
 
-    private static <T> T nonNull(@Nullable T result) {
-        Assert.state(result != null, "No result");
-        return result;
+    private static <T> T nonNull(T result) {
+        Assert.state(result != null, 'No result')
+        return result
     }
 
     /**
@@ -1030,43 +993,44 @@ public class HibernateTemplate implements HibernateOperations, InitializingBean
      */
     private class CloseSuppressingInvocationHandler implements InvocationHandler {
 
-        private final Session target;
+        private final Session target
 
-        public CloseSuppressingInvocationHandler(Session target) {
-            this.target = target;
+        CloseSuppressingInvocationHandler(Session target) {
+            this.target = target
         }
 
         @Override
-        @Nullable
-        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             // Invocation on Session interface coming in...
 
-            return switch (method.getName()) {
-                // Only consider equal when proxies are identical.
-                case "equals" -> (proxy == args[0]);
-                // Use hashCode of Session proxy.
-                case "hashCode" -> System.identityHashCode(proxy);
-                // Handle close method: suppress, not valid.
-                case "close" -> null;
-                default -> {
+            switch (method.getName()) {
+                case 'equals':
+                    // Only consider equal when proxies are identical.
+                    return proxy.is(args[0])
+                case 'hashCode':
+                    // Use hashCode of Session proxy.
+                    return System.identityHashCode(proxy)
+                case 'close':
+                    // Handle close method: suppress, not valid.
+                    return null
+                default:
                     try {
                         // Invoke method on target Session.
-                        Object retVal = method.invoke(this.target, args);
+                        Object retVal = method.invoke(this.target, args)
 
                         // If return value is a Query, apply transaction timeout.
                         // Applies to createQuery, getNamedQuery.
-                        if (retVal instanceof Query<?> query) {
-                            prepareQuery(query);
+                        if (retVal instanceof Query) {
+                            prepareQuery((Query<?>) retVal)
                         }
 
-                        yield retVal;
-                    }
-                    catch (InvocationTargetException ex) {
-                        throw ex.getTargetException();
+                        return retVal
+                    } catch (InvocationTargetException ex) {
+                        throw ex.getTargetException()
                     }
-                }
-            };
+            }
         }
+
     }
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTransactionManager.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTransactionManager.groovy
similarity index 63%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTransactionManager.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTransactionManager.groovy
index 76cfd4d2de..d1fac8cd62 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTransactionManager.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateTransactionManager.groovy
@@ -13,48 +13,49 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.grails.orm.hibernate.support.hibernate7;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.util.function.Consumer;
-
-import javax.sql.DataSource;
-
-import jakarta.persistence.PersistenceException;
-
-import org.hibernate.ConnectionReleaseMode;
-import org.hibernate.FlushMode;
-import org.hibernate.HibernateException;
-import org.hibernate.Interceptor;
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.hibernate.Transaction;
-import org.hibernate.engine.spi.SessionImplementor;
-import org.hibernate.resource.transaction.spi.TransactionStatus;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.BeanFactoryAware;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.dao.DataAccessException;
-import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.jdbc.datasource.ConnectionHolder;
-import org.springframework.jdbc.datasource.DataSourceUtils;
-import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
-import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
-import org.springframework.transaction.CannotCreateTransactionException;
-import org.springframework.transaction.IllegalTransactionStateException;
-import org.springframework.transaction.InvalidIsolationLevelException;
-import org.springframework.transaction.TransactionDefinition;
-import org.springframework.transaction.TransactionSystemException;
-import org.springframework.transaction.support.AbstractPlatformTransactionManager;
-import org.springframework.transaction.support.DefaultTransactionStatus;
-import org.springframework.transaction.support.ResourceTransactionManager;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-import org.springframework.util.Assert;
+package org.grails.orm.hibernate.support.hibernate7
+
+import java.sql.Connection
+import java.sql.ResultSet
+import java.util.function.Consumer
+
+import javax.sql.DataSource
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import jakarta.persistence.PersistenceException
+import org.hibernate.ConnectionReleaseMode
+import org.hibernate.FlushMode
+import org.hibernate.HibernateException
+import org.hibernate.Interceptor
+import org.hibernate.Session
+import org.hibernate.SessionFactory
+import org.hibernate.SharedSessionContract
+import org.hibernate.Transaction
+import org.hibernate.TransactionException
+import org.hibernate.engine.spi.SessionImplementor
+import org.hibernate.resource.transaction.spi.TransactionStatus
+import org.springframework.beans.BeansException
+import org.springframework.beans.factory.BeanFactory
+import org.springframework.beans.factory.BeanFactoryAware
+import org.springframework.beans.factory.InitializingBean
+import org.springframework.dao.DataAccessException
+import org.springframework.dao.DataAccessResourceFailureException
+import org.springframework.jdbc.datasource.ConnectionHandle
+import org.springframework.jdbc.datasource.ConnectionHolder
+import org.springframework.jdbc.datasource.DataSourceUtils
+import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport
+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
+import org.springframework.transaction.CannotCreateTransactionException
+import org.springframework.transaction.IllegalTransactionStateException
+import org.springframework.transaction.InvalidIsolationLevelException
+import org.springframework.transaction.TransactionDefinition
+import org.springframework.transaction.TransactionSystemException
+import org.springframework.transaction.support.AbstractPlatformTransactionManager
+import org.springframework.transaction.support.DefaultTransactionStatus
+import org.springframework.transaction.support.ResourceTransactionManager
+import org.springframework.transaction.support.TransactionSynchronizationManager
+import org.springframework.util.Assert
 
 /**
  * {@link org.springframework.transaction.PlatformTransactionManager}
@@ -107,67 +108,62 @@ import org.springframework.util.Assert;
  * @see org.springframework.orm.jpa.JpaTransactionManager
  * @see org.springframework.orm.jpa.vendor.HibernateJpaDialect
  */
-@SuppressWarnings("serial")
-public class HibernateTransactionManager extends AbstractPlatformTransactionManager
+@CompileStatic
+@SuppressWarnings('serial')
+class HibernateTransactionManager extends AbstractPlatformTransactionManager
         implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
 
-    @Nullable
-    private SessionFactory sessionFactory;
+    private SessionFactory sessionFactory
 
-    @Nullable
-    private DataSource dataSource;
+    private DataSource dataSource
 
-    private boolean autodetectDataSource = true;
+    private boolean autodetectDataSource = true
 
-    private boolean prepareConnection = true;
+    private boolean prepareConnection = true
 
-    private boolean allowResultAccessAfterCompletion = false;
+    private boolean allowResultAccessAfterCompletion = false
 
-    private boolean hibernateManagedSession = false;
+    private boolean hibernateManagedSession = false
 
-    @Nullable
-    private Consumer<Session> sessionInitializer;
+    private Consumer<Session> sessionInitializer
 
-    @Nullable
-    private Object entityInterceptor;
+    private Object entityInterceptor
 
     /**
      * Just needed for entityInterceptorBeanName.
      * @see #setEntityInterceptorBeanName
      */
-    @Nullable
-    private BeanFactory beanFactory;
+    private BeanFactory beanFactory
 
     /**
      * Create a new HibernateTransactionManager instance.
      * A SessionFactory has to be set to be able to use it.
      * @see #setSessionFactory
      */
-    public HibernateTransactionManager() {
+    HibernateTransactionManager() {
     }
 
     /**
      * Create a new HibernateTransactionManager instance.
      * @param sessionFactory the SessionFactory to manage transactions for
      */
-    public HibernateTransactionManager(SessionFactory sessionFactory) {
-        this.sessionFactory = sessionFactory;
-        afterPropertiesSet();
+    HibernateTransactionManager(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory
+        afterPropertiesSet()
     }
 
     /**
      * Set the SessionFactory that this instance should manage transactions for.
      */
-    public void setSessionFactory(@Nullable SessionFactory sessionFactory) {
-        this.sessionFactory = sessionFactory;
+    void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory
     }
 
     /**
      * Return the SessionFactory that this instance should manage transactions for.
      */
-    @Nullable
-    public SessionFactory getSessionFactory() {
-        return this.sessionFactory;
+    SessionFactory getSessionFactory() {
+        return this.sessionFactory
     }
 
     /**
@@ -177,9 +173,9 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @since 5.0
      */
     protected final SessionFactory obtainSessionFactory() {
-        SessionFactory sessionFactory = getSessionFactory();
-        Assert.state(sessionFactory != null, "No SessionFactory set");
-        return sessionFactory;
+        SessionFactory sessionFactory = getSessionFactory()
+        Assert.state(sessionFactory != null, 'No SessionFactory set')
+        return sessionFactory
     }
 
     /**
@@ -211,24 +207,22 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy
      * @see org.springframework.jdbc.core.JdbcTemplate
      */
-    public void setDataSource(@Nullable DataSource dataSource) {
-        if (dataSource instanceof TransactionAwareDataSourceProxy proxy) {
+    void setDataSource(DataSource dataSource) {
+        if (dataSource instanceof TransactionAwareDataSourceProxy) {
             // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
             // for its underlying target DataSource, else data access code won't see
             // properly exposed transactions (i.e. transactions for the target DataSource).
-            this.dataSource = proxy.getTargetDataSource();
-        }
-        else {
-            this.dataSource = dataSource;
+            this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource()
+        } else {
+            this.dataSource = dataSource
         }
     }
 
     /**
      * Return the JDBC DataSource that this instance manages transactions for.
      */
-    @Nullable
-    public DataSource getDataSource() {
-        return this.dataSource;
+    DataSource getDataSource() {
+        return this.dataSource
     }
 
     /**
@@ -238,8 +232,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * to not expose Hibernate transactions as JDBC transactions for that DataSource.
      * @see #setDataSource
      */
-    public void setAutodetectDataSource(boolean autodetectDataSource) {
-        this.autodetectDataSource = autodetectDataSource;
+    void setAutodetectDataSource(boolean autodetectDataSource) {
+        this.autodetectDataSource = autodetectDataSource
     }
 
     /**
@@ -255,8 +249,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @see Connection#setTransactionIsolation
      * @see Connection#setReadOnly
      */
-    public void setPrepareConnection(boolean prepareConnection) {
-        this.prepareConnection = prepareConnection;
+    void setPrepareConnection(boolean prepareConnection) {
+        this.prepareConnection = prepareConnection
     }
 
     /**
@@ -272,9 +266,9 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * making it impossible to rely on ResultSet holdability. Also, Spring does not provide
      * an equivalent setting on {@link org.springframework.orm.jpa.JpaTransactionManager}.
      */
-    @Deprecated(since = "5.3.29")
-    public void setAllowResultAccessAfterCompletion(boolean allowResultAccessAfterCompletion) {
-        this.allowResultAccessAfterCompletion = allowResultAccessAfterCompletion;
+    @Deprecated(since = '5.3.29')
+    void setAllowResultAccessAfterCompletion(boolean allowResultAccessAfterCompletion) {
+        this.allowResultAccessAfterCompletion = allowResultAccessAfterCompletion
     }
 
     /**
@@ -300,8 +294,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * call (on transaction completion) in such a scenario; this is rather left up
      * to a custom CurrentSessionContext implementation (if desired).
      */
-    public void setHibernateManagedSession(boolean hibernateManagedSession) {
-        this.hibernateManagedSession = hibernateManagedSession;
+    void setHibernateManagedSession(boolean hibernateManagedSession) {
+        this.hibernateManagedSession = hibernateManagedSession
     }
 
     /**
@@ -312,8 +306,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @since 5.3
      * @see Session#enableFilter
      */
-    public void setSessionInitializer(Consumer<Session> sessionInitializer) {
-        this.sessionInitializer = sessionInitializer;
+    void setSessionInitializer(Consumer<Session> sessionInitializer) {
+        this.sessionInitializer = sessionInitializer
     }
 
     /**
@@ -330,8 +324,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @see #setBeanFactory
      * @see #setEntityInterceptor
      */
-    public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
-        this.entityInterceptor = entityInterceptorBeanName;
+    void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
+        this.entityInterceptor = entityInterceptorBeanName
     }
 
     /**
@@ -343,8 +337,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * HibernateTransactionManager.
      * @see LocalSessionFactoryBean#setEntityInterceptor
      */
-    public void setEntityInterceptor(@Nullable Interceptor entityInterceptor) {
-        this.entityInterceptor = entityInterceptor;
+    void setEntityInterceptor(Interceptor entityInterceptor) {
+        this.entityInterceptor = entityInterceptor
     }
 
     /**
@@ -357,19 +351,16 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @see #setEntityInterceptorBeanName
      * @see #setBeanFactory
      */
-    @Nullable
-    public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
-        if (this.entityInterceptor instanceof Interceptor interceptor) {
-            return interceptor;
-        }
-        else if (this.entityInterceptor instanceof String beanName) {
+    Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
+        if (this.entityInterceptor instanceof Interceptor) {
+            return (Interceptor) this.entityInterceptor
+        } else if (this.entityInterceptor instanceof String) {
             if (this.beanFactory == null) {
-                throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
+                throw new IllegalStateException('Cannot get entity interceptor via bean name if no bean factory set')
             }
-            return this.beanFactory.getBean(beanName, Interceptor.class);
-        }
-        else {
-            return null;
+            return this.beanFactory.getBean((String) this.entityInterceptor, Interceptor)
+        } else {
+            return null
         }
     }
 
@@ -379,386 +370,371 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @see #setEntityInterceptorBeanName
      */
     @Override
-    public void setBeanFactory(BeanFactory beanFactory) {
-        this.beanFactory = beanFactory;
+    void setBeanFactory(BeanFactory beanFactory) {
+        this.beanFactory = beanFactory
     }
 
     @Override
-    public void afterPropertiesSet() {
+    void afterPropertiesSet() {
         if (getSessionFactory() == null) {
-            throw new IllegalArgumentException("Property 'sessionFactory' is required");
+            throw new IllegalArgumentException("Property 'sessionFactory' is required")
         }
         if (this.entityInterceptor instanceof String && this.beanFactory == null) {
-            throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'");
+            throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'")
         }
 
         // Check for SessionFactory's DataSource.
         if (this.autodetectDataSource && getDataSource() == null) {
-            DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory());
+            DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory())
             if (sfds != null) {
                 // Use the SessionFactory's DataSource for exposing transactions to JDBC code.
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Using DataSource [" + sfds +
-                            "] of Hibernate SessionFactory for HibernateTransactionManager");
+                    logger.debug('Using DataSource [' + sfds +
+                            '] of Hibernate SessionFactory for HibernateTransactionManager')
                 }
-                setDataSource(sfds);
+                setDataSource(sfds)
             }
         }
     }
 
     @Override
-    public Object getResourceFactory() {
-        return obtainSessionFactory();
+    Object getResourceFactory() {
+        return obtainSessionFactory()
     }
 
     @Override
     protected Object doGetTransaction() {
-        HibernateTransactionObject txObject = new HibernateTransactionObject();
-        txObject.setSavepointAllowed(isNestedTransactionAllowed());
+        HibernateTransactionObject txObject = new HibernateTransactionObject()
+        txObject.setSavepointAllowed(isNestedTransactionAllowed())
 
-        SessionFactory sessionFactory = obtainSessionFactory();
+        SessionFactory sessionFactory = obtainSessionFactory()
         SessionHolder sessionHolder =
-                (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+                (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory)
         if (sessionHolder != null) {
             if (logger.isDebugEnabled()) {
-                logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
+                logger.debug('Found thread-bound Session [' + sessionHolder.getSession() + '] for Hibernate transaction')
             }
-            txObject.setSessionHolder(sessionHolder);
-        }
-        else if (this.hibernateManagedSession) {
+            txObject.setSessionHolder(sessionHolder)
+        } else if (this.hibernateManagedSession) {
             try {
-                Session session = sessionFactory.getCurrentSession();
+                Session session = sessionFactory.getCurrentSession()
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
+                    logger.debug('Found Hibernate-managed Session [' + session + '] for Spring-managed transaction')
                 }
-                txObject.setExistingSession(session);
-            }
-            catch (HibernateException ex) {
+                txObject.setExistingSession(session)
+            } catch (HibernateException ex) {
                 throw new DataAccessResourceFailureException(
-                        "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
+                        'Could not obtain Hibernate-managed Session for Spring-managed transaction', ex)
             }
         }
 
         if (getDataSource() != null) {
-            ConnectionHolder conHolder = (ConnectionHolder)
-                    TransactionSynchronizationManager.getResource(getDataSource());
-            txObject.setConnectionHolder(conHolder);
+            ConnectionHolder conHolder =
+                    (ConnectionHolder) TransactionSynchronizationManager.getResource(getDataSource())
+            txObject.setConnectionHolder(conHolder)
         }
 
-        return txObject;
+        return txObject
     }
 
     @Override
     protected boolean isExistingTransaction(Object transaction) {
-        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
+        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction
         return (txObject.hasSpringManagedTransaction() ||
-                (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction()));
+                (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction()))
     }
 
     @Override
     protected void doBegin(Object transaction, TransactionDefinition definition) {
-        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
+        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction
 
         if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
             throw new IllegalTransactionStateException(
-                    "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
-                    "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
-                    "It is recommended to use a single HibernateTransactionManager for all transactions " +
-                    "on a single DataSource, no matter whether Hibernate or JDBC access.");
+                    'Pre-bound JDBC Connection found! HibernateTransactionManager does not support ' +
+                    'running within DataSourceTransactionManager if told to manage the DataSource itself. ' +
+                    'It is recommended to use a single HibernateTransactionManager for all transactions ' +
+                    'on a single DataSource, no matter whether Hibernate or JDBC access.')
         }
 
-        SessionImplementor session = null;
+        SessionImplementor session = null
 
         try {
             if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
-                Interceptor entityInterceptor = getEntityInterceptor();
+                Interceptor entityInterceptor = getEntityInterceptor()
                 Session newSession = (entityInterceptor != null ?
                         obtainSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
-                        obtainSessionFactory().openSession());
+                        obtainSessionFactory().openSession())
                 if (this.sessionInitializer != null) {
-                    this.sessionInitializer.accept(newSession);
+                    this.sessionInitializer.accept(newSession)
                 }
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
+                    logger.debug('Opened new Session [' + newSession + '] for Hibernate transaction')
                 }
-                txObject.setSession(newSession);
+                txObject.setSession(newSession)
             }
 
-            session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor.class);
+            session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor)
 
-            boolean holdabilityNeeded = (this.allowResultAccessAfterCompletion && !txObject.isNewSession());
-            boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
+            boolean holdabilityNeeded = (this.allowResultAccessAfterCompletion && !txObject.isNewSession())
+            boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT)
             if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
                 if (this.prepareConnection && ConnectionReleaseMode.ON_CLOSE.equals(
                         session.getJdbcCoordinator().getLogicalConnection().getConnectionHandlingMode().getReleaseMode())) {
                     // We're allowed to change the transaction settings of the JDBC Connection.
                     if (logger.isDebugEnabled()) {
-                        logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
+                        logger.debug('Preparing JDBC Connection of Hibernate Session [' + session + ']')
                     }
-                    Connection con = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection();
-                    Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
-                    txObject.setPreviousIsolationLevel(previousIsolationLevel);
-                    txObject.setReadOnly(definition.isReadOnly());
+                    Connection con = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
+                    Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition)
+                    txObject.setPreviousIsolationLevel(previousIsolationLevel)
+                    txObject.setReadOnly(definition.isReadOnly())
                     if (holdabilityNeeded) {
-                        int currentHoldability = con.getHoldability();
+                        int currentHoldability = con.getHoldability()
                         if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
-                            txObject.setPreviousHoldability(currentHoldability);
-                            con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
+                            txObject.setPreviousHoldability(currentHoldability)
+                            con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT)
                         }
                     }
-                    txObject.connectionPrepared();
-                }
-                else {
+                    txObject.connectionPrepared()
+                } else {
                     // Not allowed to change the transaction settings of the JDBC Connection.
                     if (isolationLevelNeeded) {
                         // We should set a specific isolation level but are not allowed to...
                         throw new InvalidIsolationLevelException(
-                                "HibernateTransactionManager is not allowed to support custom isolation levels: " +
+                                'HibernateTransactionManager is not allowed to support custom isolation levels: ' +
                                 "make sure that its 'prepareConnection' flag is on (the default) and that the " +
-                                "Hibernate connection release mode is set to ON_CLOSE.");
+                                'Hibernate connection release mode is set to ON_CLOSE.')
                     }
                     if (logger.isDebugEnabled()) {
-                        logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
+                        logger.debug('Not preparing JDBC Connection of Hibernate Session [' + session + ']')
                     }
                 }
             }
 
             if (definition.isReadOnly() && txObject.isNewSession()) {
                 // Just set to MANUAL in case of a new Session for this transaction.
-                session.setHibernateFlushMode(FlushMode.MANUAL);
+                session.setHibernateFlushMode(FlushMode.MANUAL)
                 // As of 5.1, we're also setting Hibernate's read-only entity mode by default.
-                session.setDefaultReadOnly(true);
+                session.setDefaultReadOnly(true)
             }
 
             if (!definition.isReadOnly() && !txObject.isNewSession()) {
                 // We need AUTO or COMMIT for a non-read-only transaction.
-                FlushMode flushMode = session.getHibernateFlushMode();
+                FlushMode flushMode = session.getHibernateFlushMode()
                 if (FlushMode.MANUAL.equals(flushMode)) {
-                    session.setHibernateFlushMode(FlushMode.AUTO);
-                    txObject.getSessionHolder().setPreviousFlushMode(flushMode);
+                    session.setHibernateFlushMode(FlushMode.AUTO)
+                    txObject.getSessionHolder().setPreviousFlushMode(flushMode)
                 }
             }
 
-            Transaction hibTx;
+            Transaction hibTx
 
             // Register transaction timeout.
-            int timeout = determineTimeout(definition);
+            int timeout = determineTimeout(definition)
             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                 // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
                 // Applies to all statements, also to inserts, updates and deletes!
-                hibTx = session.getTransaction();
-                hibTx.setTimeout(timeout);
-                hibTx.begin();
-            }
-            else {
+                // Cast to SharedSessionContract so Groovy resolves Hibernate's covariant
+                // Transaction getTransaction() rather than EntityManager's EntityTransaction.
+                hibTx = ((SharedSessionContract) session).getTransaction()
+                hibTx.setTimeout(timeout)
+                hibTx.begin()
+            } else {
                 // Open a plain Hibernate transaction without specified timeout.
-                hibTx = session.beginTransaction();
+                hibTx = session.beginTransaction()
             }
 
             // Add the Hibernate transaction to the session holder.
-            txObject.getSessionHolder().setTransaction(hibTx);
+            txObject.getSessionHolder().setTransaction(hibTx)
 
             // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
             if (getDataSource() != null) {
-                final SessionImplementor sessionToUse = session;
+                final SessionImplementor sessionToUse = session
                 ConnectionHolder conHolder = new ConnectionHolder(
-                        () -> sessionToUse.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection());
+                        { -> sessionToUse.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() } as ConnectionHandle)
                 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
-                    conHolder.setTimeoutInSeconds(timeout);
+                    conHolder.setTimeoutInSeconds(timeout)
                 }
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Exposing Hibernate transaction as JDBC [" + conHolder.getConnectionHandle() + "]");
+                    logger.debug('Exposing Hibernate transaction as JDBC [' + conHolder.getConnectionHandle() + ']')
                 }
-                TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
-                txObject.setConnectionHolder(conHolder);
+                TransactionSynchronizationManager.bindResource(getDataSource(), conHolder)
+                txObject.setConnectionHolder(conHolder)
             }
 
             // Bind the session holder to the thread.
             if (txObject.isNewSessionHolder()) {
-                TransactionSynchronizationManager.bindResource(obtainSessionFactory(), txObject.getSessionHolder());
+                TransactionSynchronizationManager.bindResource(obtainSessionFactory(), txObject.getSessionHolder())
             }
-            txObject.getSessionHolder().setSynchronizedWithTransaction(true);
-        }
-
-        catch (Throwable ex) {
+            txObject.getSessionHolder().setSynchronizedWithTransaction(true)
+        } catch (Throwable ex) {
             if (txObject.isNewSession()) {
                 try {
-                    if (session != null && session.getTransaction().getStatus() == TransactionStatus.ACTIVE) {
-                        session.getTransaction().rollback();
+                    if (session != null &&
+                            ((SharedSessionContract) session).getTransaction().getStatus() == TransactionStatus.ACTIVE) {
+                        ((SharedSessionContract) session).getTransaction().rollback()
                     }
-                }
-                catch (Throwable ex2) {
-                    logger.debug("Could not rollback Session after failed transaction begin", ex);
-                }
-                finally {
-                    SessionFactoryUtils.closeSession(session);
-                    txObject.setSessionHolder(null);
+                } catch (Throwable ex2) {
+                    logger.debug('Could not rollback Session after failed transaction begin', ex)
+                } finally {
+                    SessionFactoryUtils.closeSession(session)
+                    txObject.setSessionHolder(null)
                 }
             }
-            throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
+            throw new CannotCreateTransactionException('Could not open Hibernate Session for transaction', ex)
         }
     }
 
     @Override
     protected Object doSuspend(Object transaction) {
-        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
-        txObject.setSessionHolder(null);
+        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction
+        txObject.setSessionHolder(null)
         SessionHolder sessionHolder =
-                (SessionHolder) TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
-        txObject.setConnectionHolder(null);
-        ConnectionHolder connectionHolder = null;
+                (SessionHolder) TransactionSynchronizationManager.unbindResource(obtainSessionFactory())
+        txObject.setConnectionHolder(null)
+        ConnectionHolder connectionHolder = null
         if (getDataSource() != null) {
-            connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
+            connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource())
         }
-        return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
+        return new SuspendedResourcesHolder(sessionHolder, connectionHolder)
     }
 
     @Override
-    protected void doResume(@Nullable Object transaction, Object suspendedResources) {
-        SessionFactory sessionFactory = obtainSessionFactory();
+    protected void doResume(Object transaction, Object suspendedResources) {
+        SessionFactory sessionFactory = obtainSessionFactory()
 
-        SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
+        SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources
         if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
             // From non-transactional code running in active transaction synchronization
             // -> can be safely removed, will be closed on transaction completion.
-            TransactionSynchronizationManager.unbindResource(sessionFactory);
+            TransactionSynchronizationManager.unbindResource(sessionFactory)
         }
-        TransactionSynchronizationManager.bindResource(sessionFactory, resourcesHolder.getSessionHolder());
-        ConnectionHolder connectionHolder = resourcesHolder.getConnectionHolder();
+        TransactionSynchronizationManager.bindResource(sessionFactory, resourcesHolder.getSessionHolder())
+        ConnectionHolder connectionHolder = resourcesHolder.getConnectionHolder()
         if (connectionHolder != null && getDataSource() != null) {
-            TransactionSynchronizationManager.bindResource(getDataSource(), connectionHolder);
+            TransactionSynchronizationManager.bindResource(getDataSource(), connectionHolder)
         }
     }
 
     @Override
     protected void doCommit(DefaultTransactionStatus status) {
-        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
-        Transaction hibTx = txObject.getSessionHolder().getTransaction();
-        Assert.state(hibTx != null, "No Hibernate transaction");
+        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction()
+        Transaction hibTx = txObject.getSessionHolder().getTransaction()
+        Assert.state(hibTx != null, 'No Hibernate transaction')
         if (status.isDebug()) {
-            logger.debug("Committing Hibernate transaction on Session [" +
-                    txObject.getSessionHolder().getSession() + "]");
+            logger.debug('Committing Hibernate transaction on Session [' +
+                    txObject.getSessionHolder().getSession() + ']')
         }
 
         try {
-            hibTx.commit();
-        }
-        catch (org.hibernate.TransactionException ex) {
+            hibTx.commit()
+        } catch (TransactionException ex) {
             // assumably from commit call to the underlying JDBC connection
-            throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
-        }
-        catch (HibernateException ex) {
+            throw new TransactionSystemException('Could not commit Hibernate transaction', ex)
+        } catch (HibernateException ex) {
             // assumably failed to flush changes to database
-            throw convertHibernateAccessException(ex);
-        }
-        catch (PersistenceException ex) {
-            if (ex.getCause() instanceof HibernateException hibernateEx) {
-                throw convertHibernateAccessException(hibernateEx);
+            throw convertHibernateAccessException(ex)
+        } catch (PersistenceException ex) {
+            if (ex.getCause() instanceof HibernateException) {
+                throw convertHibernateAccessException((HibernateException) ex.getCause())
             }
-            throw ex;
+            throw ex
         }
     }
 
     @Override
     protected void doRollback(DefaultTransactionStatus status) {
-        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
-        Transaction hibTx = txObject.getSessionHolder().getTransaction();
-        Assert.state(hibTx != null, "No Hibernate transaction");
+        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction()
+        Transaction hibTx = txObject.getSessionHolder().getTransaction()
+        Assert.state(hibTx != null, 'No Hibernate transaction')
         if (status.isDebug()) {
-            logger.debug("Rolling back Hibernate transaction on Session [" +
-                    txObject.getSessionHolder().getSession() + "]");
+            logger.debug('Rolling back Hibernate transaction on Session [' +
+                    txObject.getSessionHolder().getSession() + ']')
         }
 
         try {
-            hibTx.rollback();
-        }
-        catch (org.hibernate.TransactionException ex) {
-            throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
-        }
-        catch (HibernateException ex) {
+            hibTx.rollback()
+        } catch (TransactionException ex) {
+            throw new TransactionSystemException('Could not roll back Hibernate transaction', ex)
+        } catch (HibernateException ex) {
             // Shouldn't really happen, as a rollback doesn't cause a flush.
-            throw convertHibernateAccessException(ex);
-        }
-        catch (PersistenceException ex) {
-            if (ex.getCause() instanceof HibernateException hibernateEx) {
-                throw convertHibernateAccessException(hibernateEx);
+            throw convertHibernateAccessException(ex)
+        } catch (PersistenceException ex) {
+            if (ex.getCause() instanceof HibernateException) {
+                throw convertHibernateAccessException((HibernateException) ex.getCause())
             }
-            throw ex;
-        }
-        finally {
+            throw ex
+        } finally {
             if (!txObject.isNewSession() && !this.hibernateManagedSession) {
                 // Clear all pending inserts/updates/deletes in the Session.
                 // Necessary for pre-bound Sessions, to avoid inconsistent state.
-                txObject.getSessionHolder().getSession().clear();
+                txObject.getSessionHolder().getSession().clear()
             }
         }
     }
 
     @Override
     protected void doSetRollbackOnly(DefaultTransactionStatus status) {
-        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
+        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction()
         if (status.isDebug()) {
-            logger.debug("Setting Hibernate transaction on Session [" +
-                    txObject.getSessionHolder().getSession() + "] rollback-only");
+            logger.debug('Setting Hibernate transaction on Session [' +
+                    txObject.getSessionHolder().getSession() + '] rollback-only')
         }
-        txObject.setRollbackOnly();
+        txObject.setRollbackOnly()
     }
 
     @Override
     protected void doCleanupAfterCompletion(Object transaction) {
-        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
+        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction
 
         // Remove the session holder from the thread.
         if (txObject.isNewSessionHolder()) {
-            TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
+            TransactionSynchronizationManager.unbindResource(obtainSessionFactory())
         }
 
         // Remove the JDBC connection holder from the thread, if exposed.
         if (getDataSource() != null) {
-            TransactionSynchronizationManager.unbindResource(getDataSource());
+            TransactionSynchronizationManager.unbindResource(getDataSource())
         }
 
-        SessionImplementor session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor.class);
+        SessionImplementor session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor)
         if (txObject.needsConnectionReset() &&
                 session.getJdbcCoordinator().getLogicalConnection().isPhysicallyConnected()) {
             // We're running with connection release mode ON_CLOSE: We're able to reset
             // the isolation level and/or read-only flag of the JDBC Connection here.
             // Else, we need to rely on the connection pool to perform proper cleanup.
             try {
-                Connection con = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection();
-                Integer previousHoldability = txObject.getPreviousHoldability();
+                Connection con = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
+                Integer previousHoldability = txObject.getPreviousHoldability()
                 if (previousHoldability != null) {
-                    con.setHoldability(previousHoldability);
+                    con.setHoldability(previousHoldability)
                 }
                 DataSourceUtils.resetConnectionAfterTransaction(
-                        con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
-            }
-            catch (HibernateException ex) {
-                logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
-            }
-            catch (Throwable ex) {
-                logger.debug("Could not reset JDBC Connection after transaction", ex);
+                        con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly())
+            } catch (HibernateException ex) {
+                logger.debug('Could not access JDBC Connection of Hibernate Session', ex)
+            } catch (Throwable ex) {
+                logger.debug('Could not reset JDBC Connection after transaction', ex)
             }
         }
 
         if (txObject.isNewSession()) {
             if (logger.isDebugEnabled()) {
-                logger.debug("Closing Hibernate Session [" + session + "] after transaction");
+                logger.debug('Closing Hibernate Session [' + session + '] after transaction')
             }
-            SessionFactoryUtils.closeSession(session);
-        }
-        else {
+            SessionFactoryUtils.closeSession(session)
+        } else {
             if (logger.isDebugEnabled()) {
-                logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction");
+                logger.debug('Not closing pre-bound Hibernate Session [' + session + '] after transaction')
             }
             if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
-                session.setHibernateFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
+                session.setHibernateFlushMode(txObject.getSessionHolder().getPreviousFlushMode())
             }
             if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
-                disconnectOnCompletion(session);
+                disconnectOnCompletion(session)
             }
         }
-        txObject.getSessionHolder().clear();
+        txObject.getSessionHolder().clear()
     }
 
     /**
@@ -769,8 +745,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @see Session#disconnect()
      */
     protected void disconnectOnCompletion(Session session) {
-        if (session instanceof SessionImplementor sessionImpl) {
-            sessionImpl.getJdbcCoordinator().getLogicalConnection().manualDisconnect();
+        if (session instanceof SessionImplementor) {
+            ((SessionImplementor) session).getJdbcCoordinator().getLogicalConnection().manualDisconnect()
         }
     }
 
@@ -782,7 +758,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      * @see SessionFactoryUtils#convertHibernateAccessException
      */
     protected DataAccessException convertHibernateAccessException(HibernateException ex) {
-        return SessionFactoryUtils.convertHibernateAccessException(ex);
+        return SessionFactoryUtils.convertHibernateAccessException(ex)
     }
 
     /**
@@ -791,107 +767,104 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      */
     private class HibernateTransactionObject extends JdbcTransactionObjectSupport {
 
-        @Nullable
-        private SessionHolder sessionHolder;
+        private SessionHolder sessionHolder
 
-        private boolean newSessionHolder;
+        private boolean newSessionHolder
 
-        private boolean newSession;
+        private boolean newSession
 
-        private boolean needsConnectionReset;
+        private boolean needsConnectionReset
 
-        @Nullable
-        private Integer previousHoldability;
+        private Integer previousHoldability
 
-        public void setSession(Session session) {
-            this.sessionHolder = new SessionHolder(session);
-            this.newSessionHolder = true;
-            this.newSession = true;
+        void setSession(Session session) {
+            this.sessionHolder = new SessionHolder(session)
+            this.newSessionHolder = true
+            this.newSession = true
         }
 
-        public void setExistingSession(Session session) {
-            this.sessionHolder = new SessionHolder(session);
-            this.newSessionHolder = true;
-            this.newSession = false;
+        void setExistingSession(Session session) {
+            this.sessionHolder = new SessionHolder(session)
+            this.newSessionHolder = true
+            this.newSession = false
         }
 
-        public void setSessionHolder(@Nullable SessionHolder sessionHolder) {
-            this.sessionHolder = sessionHolder;
-            this.newSessionHolder = false;
-            this.newSession = false;
+        void setSessionHolder(SessionHolder sessionHolder) {
+            this.sessionHolder = sessionHolder
+            this.newSessionHolder = false
+            this.newSession = false
         }
 
-        public SessionHolder getSessionHolder() {
-            Assert.state(this.sessionHolder != null, "No SessionHolder available");
-            return this.sessionHolder;
+        SessionHolder getSessionHolder() {
+            Assert.state(this.sessionHolder != null, 'No SessionHolder available')
+            return this.sessionHolder
         }
 
-        public boolean hasSessionHolder() {
-            return (this.sessionHolder != null);
+        boolean hasSessionHolder() {
+            return (this.sessionHolder != null)
         }
 
-        public boolean isNewSessionHolder() {
-            return this.newSessionHolder;
+        boolean isNewSessionHolder() {
+            return this.newSessionHolder
         }
 
-        public boolean isNewSession() {
-            return this.newSession;
+        boolean isNewSession() {
+            return this.newSession
         }
 
-        public void connectionPrepared() {
-            this.needsConnectionReset = true;
+        void connectionPrepared() {
+            this.needsConnectionReset = true
         }
 
-        public boolean needsConnectionReset() {
-            return this.needsConnectionReset;
+        boolean needsConnectionReset() {
+            return this.needsConnectionReset
         }
 
-        public void setPreviousHoldability(@Nullable Integer previousHoldability) {
-            this.previousHoldability = previousHoldability;
+        void setPreviousHoldability(Integer previousHoldability) {
+            this.previousHoldability = previousHoldability
         }
 
-        @Nullable
-        public Integer getPreviousHoldability() {
-            return this.previousHoldability;
+        Integer getPreviousHoldability() {
+            return this.previousHoldability
         }
 
-        public boolean hasSpringManagedTransaction() {
-            return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
+        boolean hasSpringManagedTransaction() {
+            return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null)
         }
 
-        public boolean hasHibernateManagedTransaction() {
+        boolean hasHibernateManagedTransaction() {
             return (this.sessionHolder != null &&
-                    this.sessionHolder.getSession().getTransaction().getStatus() == TransactionStatus.ACTIVE);
+                    ((SharedSessionContract) this.sessionHolder.getSession()).getTransaction().getStatus() ==
+                            TransactionStatus.ACTIVE)
         }
 
-        public void setRollbackOnly() {
-            getSessionHolder().setRollbackOnly();
+        void setRollbackOnly() {
+            getSessionHolder().setRollbackOnly()
             if (hasConnectionHolder()) {
-                getConnectionHolder().setRollbackOnly();
+                getConnectionHolder().setRollbackOnly()
             }
         }
 
         @Override
-        public boolean isRollbackOnly() {
+        boolean isRollbackOnly() {
             return getSessionHolder().isRollbackOnly() ||
-                    (hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
+                    (hasConnectionHolder() && getConnectionHolder().isRollbackOnly())
         }
 
         @Override
-        public void flush() {
+        void flush() {
             try {
-                getSessionHolder().getSession().flush();
-            }
-            catch (HibernateException ex) {
-                throw convertHibernateAccessException(ex);
-            }
-            catch (PersistenceException ex) {
-                if (ex.getCause() instanceof HibernateException hibernateEx) {
-                    throw convertHibernateAccessException(hibernateEx);
+                getSessionHolder().getSession().flush()
+            } catch (HibernateException ex) {
+                throw HibernateTransactionManager.this.convertHibernateAccessException(ex)
+            } catch (PersistenceException ex) {
+                if (ex.getCause() instanceof HibernateException) {
+                    throw HibernateTransactionManager.this.convertHibernateAccessException((HibernateException) ex.getCause())
                 }
-                throw ex;
+                throw ex
             }
         }
+
     }
 
     /**
@@ -900,24 +873,26 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
      */
     private static final class SuspendedResourcesHolder {
 
-        private final SessionHolder sessionHolder;
+        private final SessionHolder sessionHolder
 
-        @Nullable
-        private final ConnectionHolder connectionHolder;
+        private final ConnectionHolder connectionHolder
 
-        private SuspendedResourcesHolder(SessionHolder sessionHolder, @Nullable ConnectionHolder conHolder) {
-            this.sessionHolder = sessionHolder;
-            this.connectionHolder = conHolder;
+        @PackageScope
+        SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
+            this.sessionHolder = sessionHolder
+            this.connectionHolder = conHolder
         }
 
-        private SessionHolder getSessionHolder() {
-            return this.sessionHolder;
+        @PackageScope
+        SessionHolder getSessionHolder() {
+            return this.sessionHolder
         }
 
-        @Nullable
-        private ConnectionHolder getConnectionHolder() {
-            return this.connectionHolder;
+        @PackageScope
+        ConnectionHolder getConnectionHolder() {
+            return this.connectionHolder
         }
+
     }
 
 }


(grails-core) 04/14: Convert GormSpringDataSessionSupport to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 9c3f86bacb97d13a16e7727fd7a908749df7ffe3
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 17:12:35 2026 -0500

    Convert GormSpringDataSessionSupport to Groovy
    
    Completes the spring-data module; src/main/java is removed as this was
    its only file. The class stays in org.springframework.data.mongodb for
    the same reason as before: it needs Spring Data's package-private
    MongoResourceHolder constructor.
    
    Verified: spring-data build (compile, codeStyle, 13 tests) green.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 .../mongodb/GormSpringDataSessionSupport.groovy}     | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/grails-data-mongodb/spring-data/src/main/java/org/springframework/data/mongodb/GormSpringDataSessionSupport.java b/grails-data-mongodb/spring-data/src/main/groovy/org/springframework/data/mongodb/GormSpringDataSessionSupport.groovy
similarity index 87%
rename from grails-data-mongodb/spring-data/src/main/java/org/springframework/data/mongodb/GormSpringDataSessionSupport.java
rename to grails-data-mongodb/spring-data/src/main/groovy/org/springframework/data/mongodb/GormSpringDataSessionSupport.groovy
index 984cf826f4..e459f1d65a 100644
--- a/grails-data-mongodb/spring-data/src/main/java/org/springframework/data/mongodb/GormSpringDataSessionSupport.java
+++ b/grails-data-mongodb/spring-data/src/main/groovy/org/springframework/data/mongodb/GormSpringDataSessionSupport.groovy
@@ -16,11 +16,11 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.springframework.data.mongodb;
+package org.springframework.data.mongodb
 
-import com.mongodb.client.ClientSession;
-
-import org.springframework.transaction.support.TransactionSynchronizationManager;
+import com.mongodb.client.ClientSession
+import groovy.transform.CompileStatic
+import org.springframework.transaction.support.TransactionSynchronizationManager
 
 /**
  * Binds a GORM-owned {@link ClientSession} into Spring Data MongoDB's thread-bound resources so that
@@ -46,7 +46,8 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
  *
  * @since 8.0
  */
-public final class GormSpringDataSessionSupport {
+@CompileStatic
+final class GormSpringDataSessionSupport {
 
     private GormSpringDataSessionSupport() {
     }
@@ -54,11 +55,12 @@ public final class GormSpringDataSessionSupport {
     /**
      * Binds the given session to the current thread for the given factory, if nothing is bound yet.
      */
-    public static void bindClientSession(MongoDatabaseFactory databaseFactory, ClientSession clientSession) {
+    static void bindClientSession(MongoDatabaseFactory databaseFactory, ClientSession clientSession) {
         if (!TransactionSynchronizationManager.hasResource(databaseFactory)) {
-            MongoResourceHolder holder = new MongoResourceHolder(clientSession, databaseFactory);
-            holder.setSynchronizedWithTransaction(true);
-            TransactionSynchronizationManager.bindResource(databaseFactory, holder);
+            MongoResourceHolder holder = new MongoResourceHolder(clientSession, databaseFactory)
+            holder.setSynchronizedWithTransaction(true)
+            TransactionSynchronizationManager.bindResource(databaseFactory, holder)
         }
     }
+
 }


(grails-core) 03/14: Convert MongoQuery to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 8654cfcf1f299406efb7b0e1795a5e206b66804e
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 17:12:34 2026 -0500

    Convert MongoQuery to Groovy
    
    Sixth grails-data-mongodb batch: MongoQuery, with its static handler
    registrations, the geo criterion classes, AggregatedResultList,
    MongoResultList and AggregatePipeline.
    
    Two runtime-relevant adjustments. In the unique-result path the Java code
    cast the iterable to FindIterable<Document> and read first() into an
    Object; that cast is erased in Java, but the Groovy static compiler turns
    it into a real check against Document, and with the codec persister the
    collection was re-typed to the entity class so first() returns an entity
    (117 suite failures). The cast is raw there so first() stays Object. The
    WithinBox/WithinPolygon/WithinCircle setValue(List) overloads assign the
    inherited field as this.@value: property syntax would route the
    assignment back into the setter.
    
    Otherwise: BsonQuery's nested handler interfaces and Query's nested
    criterion/projection classes are imported explicitly; the private static
    helpers used from the anonymous handlers are @PackageScope; the "in"
    parameter is renamed; AggregatedResultList's members used by its
    anonymous iterator are @PackageScope; createAssociationMap uses Groovy's
    withDefault directly instead of DefaultGroovyMethods with hand-built
    Closure subclasses.
    
    Verified: compileGroovy, codenarc and checkstyle clean; full core suite
    identical to baseline (669 tests, 0 failures, 45 skipped).
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 .../mapping/mongo/query/MongoQuery.groovy          | 1658 ++++++++++++++++++++
 .../datastore/mapping/mongo/query/MongoQuery.java  | 1633 -------------------
 2 files changed, 1658 insertions(+), 1633 deletions(-)

diff --git a/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.groovy b/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.groovy
new file mode 100644
index 0000000000..19ec924b14
--- /dev/null
+++ b/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.groovy
@@ -0,0 +1,1658 @@
+/* Copyright (C) 2010-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.grails.datastore.mapping.mongo.query
+
+import com.mongodb.ReadConcern
+import com.mongodb.client.AggregateIterable
+import com.mongodb.client.FindIterable
+import com.mongodb.client.MongoCursor
+import com.mongodb.client.MongoIterable
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.bson.BsonDocument
+import org.bson.BsonDocumentReader
+import org.bson.BsonDocumentWriter
+import org.bson.Document
+import org.bson.codecs.configuration.CodecRegistry
+import org.springframework.dao.InvalidDataAccessResourceUsageException
+import org.springframework.transaction.support.TransactionSynchronizationManager
+
+import grails.mongodb.geo.Box
+import grails.mongodb.geo.Circle
+import grails.mongodb.geo.Distance
+import grails.mongodb.geo.GeoJSON
+import grails.mongodb.geo.Point
+import grails.mongodb.geo.Polygon
+import grails.mongodb.geo.Shape
+import grails.mongodb.geo.Sphere
+import org.grails.datastore.bson.codecs.CodecCustomTypeMarshaller
+import org.grails.datastore.bson.query.BsonQuery
+import org.grails.datastore.bson.query.BsonQuery.ProjectionHandler
+import org.grails.datastore.bson.query.BsonQuery.QueryHandler
+import org.grails.datastore.bson.query.EmbeddedQueryEncoder
+import org.grails.datastore.gorm.mongo.geo.GeoJSONType
+import org.grails.datastore.gorm.query.AbstractResultList
+import org.grails.datastore.mapping.core.SessionImplementor
+import org.grails.datastore.mapping.engine.EntityAccess
+import org.grails.datastore.mapping.engine.EntityPersister
+import org.grails.datastore.mapping.engine.types.CustomTypeMarshaller
+import org.grails.datastore.mapping.model.EmbeddedPersistentEntity
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.datastore.mapping.model.PersistentProperty
+import org.grails.datastore.mapping.model.types.Association
+import org.grails.datastore.mapping.model.types.Basic
+import org.grails.datastore.mapping.model.types.Custom
+import org.grails.datastore.mapping.model.types.Embedded
+import org.grails.datastore.mapping.model.types.EmbeddedCollection
+import org.grails.datastore.mapping.model.types.ToOne
+import org.grails.datastore.mapping.mongo.AbstractMongoSession
+import org.grails.datastore.mapping.mongo.MongoCodecSession
+import org.grails.datastore.mapping.mongo.MongoDatastore
+import org.grails.datastore.mapping.mongo.config.MongoCollection
+import org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister
+import org.grails.datastore.mapping.mongo.engine.MongoEntityPersister
+import org.grails.datastore.mapping.mongo.engine.MongoIdCoercion
+import org.grails.datastore.mapping.mongo.engine.codecs.PersistentEntityCodec
+import org.grails.datastore.mapping.query.AssociationQuery
+import org.grails.datastore.mapping.query.Query
+import org.grails.datastore.mapping.query.Query.AvgProjection
+import org.grails.datastore.mapping.query.Query.Conjunction
+import org.grails.datastore.mapping.query.Query.CountDistinctProjection
+import org.grails.datastore.mapping.query.Query.CountProjection
+import org.grails.datastore.mapping.query.Query.Criterion
+import org.grails.datastore.mapping.query.Query.Disjunction
+import org.grails.datastore.mapping.query.Query.DistinctProjection
+import org.grails.datastore.mapping.query.Query.DistinctPropertyProjection
+import org.grails.datastore.mapping.query.Query.IdEquals
+import org.grails.datastore.mapping.query.Query.IdProjection
+import org.grails.datastore.mapping.query.Query.In
+import org.grails.datastore.mapping.query.Query.Junction
+import org.grails.datastore.mapping.query.Query.MaxProjection
+import org.grails.datastore.mapping.query.Query.MinProjection
+import org.grails.datastore.mapping.query.Query.Order
+import org.grails.datastore.mapping.query.Query.Projection
+import org.grails.datastore.mapping.query.Query.PropertyCriterion
+import org.grails.datastore.mapping.query.Query.PropertyProjection
+import org.grails.datastore.mapping.query.Query.SumProjection
+import org.grails.datastore.mapping.query.api.QueryArgumentsAware
+import org.grails.datastore.mapping.query.projections.ManualProjections
+
+/**
+ * A {@link org.grails.datastore.mapping.query.Query} implementation for the Mongo document store.
+ *
+ * @author Graeme Rocher
+ * @since 1.0
+ */
+@CompileStatic
+@SuppressWarnings('rawtypes')
+class MongoQuery extends BsonQuery implements QueryArgumentsAware {
+
+    public static final String MONGO_IN_OPERATOR = IN_OPERATOR
+    public static final String MONGO_OR_OPERATOR = OR_OPERATOR
+    public static final String MONGO_AND_OPERATOR = AND_OPERATOR
+    public static final String MONGO_GTE_OPERATOR = GTE_OPERATOR
+    public static final String MONGO_LTE_OPERATOR = LTE_OPERATOR
+    public static final String MONGO_GT_OPERATOR = GT_OPERATOR
+    public static final String MONGO_LT_OPERATOR = LT_OPERATOR
+    public static final String MONGO_NE_OPERATOR = NE_OPERATOR
+    public static final String MONGO_NIN_OPERATOR = NIN_OPERATOR
+    public static final String MONGO_REGEX_OPERATOR = REGEX_OPERATOR
+
+    public static final String MONGO_WHERE_OPERATOR = WHERE_OPERATOR
+
+    public static final String HINT_ARGUMENT = 'hint'
+    public static final String READ_CONCERN_ARGUMENT = 'readConcern'
+
+    private Map queryArguments = Collections.emptyMap()
+
+    public static final String NEAR_OPERATOR = '$near'
+
+    public static final String BOX_OPERATOR = '$box'
+
+    public static final String POLYGON_OPERATOR = '$polygon'
+
+    public static final String WITHIN_OPERATOR = '$within'
+
+    public static final String CENTER_OPERATOR = '$center'
+
+    public static final String GEO_WITHIN_OPERATOR = '$geoWithin'
+
+    public static final String GEOMETRY_OPERATOR = '$geometry'
+
+    public static final String CENTER_SPHERE_OPERATOR = '$centerSphere'
+
+    public static final String GEO_INTERSECTS_OPERATOR = '$geoIntersects'
+
+    public static final String MAX_DISTANCE_OPERATOR = '$maxDistance'
+
+    public static final String NEAR_SPHERE_OPERATOR = '$nearSphere'
+
+    static {
+        queryHandlers.put(IdEquals, new QueryHandler<IdEquals>() {
+            // Exercised end-to-end by StringIdWithObjectIdStorageSpec:
+            //   - "with storedAs ObjectId, point lookup by hex string works" (happy path)
+            //   - "with storedAs ObjectId, point lookup of a non-hex id matches the
+            //     BSON String the encoder wrote" (null-return fallback for natural keys)
+            //   - "with storedAs ObjectId, updates persist (no phantom OptimisticLockingException)"
+            //     and related update/delete specs (which also hit IdEquals via the session filter)
+            void handle(EmbeddedQueryEncoder queryEncoder, IdEquals criterion, Document query, PersistentEntity entity) {
+                Object value = criterion.getValue()
+                // Prefer the configured storage type ('storedAs' on the id mapping) so query BSON
+                // matches what's actually on disk. Falls back to the declared Java type otherwise.
+                Class<?> storedAs = MongoIdCoercion.resolveStoredAs(entity)
+                Class<?> targetType = storedAs != null ? storedAs : entity.getIdentity().getType()
+                Object converted = entity.getMappingContext().getConversionService().convert(value, targetType)
+                // Symmetry with IdentityEncoder's non-hex fallback: if the converter returns
+                // null for a non-null input (e.g. a natural-key String being converted to
+                // ObjectId), keep the original value so the query targets what the encoder
+                // actually wrote rather than {_id: null}.
+                if (converted == null && value != null) {
+                    converted = value
+                }
+                query.put(MongoEntityPersister.MONGO_ID_FIELD, converted)
+            }
+        })
+
+        // Override the In handler so that criteria targeting the identity (findAllByIdInList,
+        // Domain.createCriteria().list { 'in'('id', [...]) }, etc.) honor 'storedAs' the same way
+        // IdEquals does. Without this, a domain declaring storedAs: ObjectId would send BSON Strings
+        // in {_id: {$in: [...]}} and miss all stored ObjectId documents.
+        //
+        // Exercised end-to-end by StringIdWithObjectIdStorageSpec:
+        //   - "with storedAs ObjectId, findAllByIdInList resolves all ids" (happy path via dynamic finder)
+        //   - "with storedAs ObjectId, criteria in('id', [...]) resolves all ids" (happy path via createCriteria)
+        //   - "with storedAs ObjectId, batch getAll with non-hex ids falls back to BSON String in the in-list"
+        //     (null-return fallback for natural keys)
+        queryHandlers.put(In, new QueryHandler<In>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, In inCriterion, Document query, PersistentEntity entity) {
+                Document inQuery = new Document()
+                List<Object> values = getInListQueryValues(entity, inCriterion)
+
+                PersistentProperty identityProp = entity.getIdentity()
+                boolean isIdInList = identityProp != null && identityProp.getName() == inCriterion.getProperty()
+                if (isIdInList && MongoIdCoercion.resolveStoredAs(entity) != null) {
+                    List<Object> coerced = new ArrayList<Object>(values.size())
+                    for (Object v : values) {
+                        coerced.add(MongoIdCoercion.coerceIdToStoredType(v, entity))
+                    }
+                    values = coerced
+                }
+
+                inQuery.put(IN_OPERATOR, values)
+                String propertyName = getPropertyName(entity, inCriterion)
+                query.put(propertyName, inQuery)
+            }
+        })
+
+        queryHandlers.put(AssociationQuery, new QueryHandler<AssociationQuery>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, AssociationQuery criterion, Document query, PersistentEntity entity) {
+                Association<?> association = criterion.getAssociation()
+                PersistentEntity associatedEntity = association.getAssociatedEntity()
+                if (association instanceof EmbeddedCollection) {
+                    Document associationCollectionQuery = new Document()
+                    populateMongoQuery(queryEncoder, associationCollectionQuery, criterion.getCriteria(), associatedEntity)
+                    Document collectionQuery = new Document('$elemMatch', associationCollectionQuery)
+                    String propertyKey = getPropertyName(entity, association.getName())
+                    query.put(propertyKey, collectionQuery)
+                } else if (associatedEntity instanceof EmbeddedPersistentEntity || association instanceof Embedded) {
+                    Document associatedEntityQuery = new Document()
+                    populateMongoQuery(queryEncoder, associatedEntityQuery, criterion.getCriteria(), associatedEntity)
+                    String propertyKey = getPropertyName(entity, association.getName())
+                    prefixEmbeddedQuery(propertyKey, associatedEntityQuery, query)
+                } else {
+                    throw new UnsupportedOperationException('Join queries are not supported by MongoDB')
+                }
+            }
+        })
+
+        queryHandlers.put(WithinBox, new QueryHandler<WithinBox>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, WithinBox withinBox, Document query, PersistentEntity entity) {
+                Document nearQuery = new Document()
+                Document box = new Document()
+                MongoEntityPersister.setDBObjectValue(box, BOX_OPERATOR, withinBox.getValues(), entity.getMappingContext())
+                nearQuery.put(WITHIN_OPERATOR, box)
+                String propertyName = getPropertyName(entity, withinBox)
+                query.put(propertyName, nearQuery)
+            }
+        })
+
+        queryHandlers.put(WithinPolygon, new QueryHandler<WithinPolygon>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, WithinPolygon withinPolygon, Document query, PersistentEntity entity) {
+                Document nearQuery = new Document()
+                Document box = new Document()
+                MongoEntityPersister.setDBObjectValue(box, POLYGON_OPERATOR, withinPolygon.getValues(), entity.getMappingContext())
+                nearQuery.put(WITHIN_OPERATOR, box)
+                String propertyName = getPropertyName(entity, withinPolygon)
+                query.put(propertyName, nearQuery)
+            }
+        })
+
+        queryHandlers.put(WithinCircle, new QueryHandler<WithinCircle>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, WithinCircle withinCentre, Document query, PersistentEntity entity) {
+                Document nearQuery = new Document()
+                Document center = new Document()
+                MongoEntityPersister.setDBObjectValue(center, CENTER_OPERATOR, withinCentre.getValues(), entity.getMappingContext())
+                nearQuery.put(WITHIN_OPERATOR, center)
+                String propertyName = getPropertyName(entity, withinCentre)
+                query.put(propertyName, nearQuery)
+            }
+        })
+
+        QueryHandler<Near> nearHandler = new QueryHandler<Near>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Near near, Document query, PersistentEntity entity) {
+                Document nearQuery = new Document()
+                Object value = near.getValue()
+                String nearOperator = near instanceof NearSphere ? NEAR_SPHERE_OPERATOR : NEAR_OPERATOR
+                if ((value instanceof List) || (value instanceof Map)) {
+                    MongoEntityPersister.setDBObjectValue(nearQuery, nearOperator, value, entity.getMappingContext())
+                } else if (value instanceof Point) {
+                    Document geoJson = GeoJSONType.convertToGeoDocument((Point) value)
+                    Document geometry = new Document()
+                    geometry.put(GEOMETRY_OPERATOR, geoJson)
+                    if (near.maxDistance != null) {
+                        geometry.put(MAX_DISTANCE_OPERATOR, near.maxDistance.getValue())
+                    }
+                    nearQuery.put(nearOperator, geometry)
+                }
+
+                String propertyName = getPropertyName(entity, near)
+                query.put(propertyName, nearQuery)
+            }
+        }
+        queryHandlers.put(Near, nearHandler)
+        queryHandlers.put(NearSphere, nearHandler)
+
+        queryHandlers.put(GeoWithin, new QueryHandler<GeoWithin>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, GeoWithin geoWithin, Document query, PersistentEntity entity) {
+                Document queryRoot = new Document()
+                Document queryGeoWithin = new Document()
+                queryRoot.put(GEO_WITHIN_OPERATOR, queryGeoWithin)
+                String targetProperty = getPropertyName(entity, geoWithin)
+                Object value = geoWithin.getValue()
+                if (value instanceof Shape) {
+                    Shape shape = (Shape) value
+                    if (shape instanceof Polygon) {
+                        Polygon p = (Polygon) shape
+                        Document geoJson = GeoJSONType.convertToGeoDocument(p)
+                        queryGeoWithin.put(GEOMETRY_OPERATOR, geoJson)
+                    } else if (shape instanceof Box) {
+                        queryGeoWithin.put(BOX_OPERATOR, shape.asList())
+                    } else if (shape instanceof Circle) {
+                        queryGeoWithin.put(CENTER_OPERATOR, shape.asList())
+                    } else if (shape instanceof Sphere) {
+                        queryGeoWithin.put(CENTER_SPHERE_OPERATOR, shape.asList())
+                    }
+                } else if (value instanceof Map) {
+                    queryGeoWithin.putAll((Map) value)
+                }
+
+                query.put(targetProperty, queryRoot)
+            }
+        })
+
+        queryHandlers.put(GeoIntersects, new QueryHandler<GeoIntersects>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, GeoIntersects geoIntersects, Document query, PersistentEntity entity) {
+                Document queryRoot = new Document()
+                Document queryGeoWithin = new Document()
+                queryRoot.put(GEO_INTERSECTS_OPERATOR, queryGeoWithin)
+                String targetProperty = getPropertyName(entity, geoIntersects)
+                Object value = geoIntersects.getValue()
+                if (value instanceof GeoJSON) {
+                    Shape shape = (Shape) value
+                    Document geoJson = GeoJSONType.convertToGeoDocument(shape)
+                    queryGeoWithin.put(GEOMETRY_OPERATOR, geoJson)
+                } else if (value instanceof Map) {
+                    queryGeoWithin.putAll((Map) value)
+                }
+
+                query.put(targetProperty, queryRoot)
+            }
+        })
+        queryHandlers.put(Conjunction, new QueryHandler<Conjunction>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Conjunction criterion, Document query, PersistentEntity entity) {
+                populateMongoQuery(queryEncoder, query, criterion, entity)
+            }
+        })
+
+        queryHandlers.put(Disjunction, new QueryHandler<Disjunction>() {
+            void handle(EmbeddedQueryEncoder queryEncoder, Disjunction criterion, Document query, PersistentEntity entity) {
+                populateMongoQuery(queryEncoder, query, criterion, entity)
+            }
+        })
+
+        groupByProjectionHandlers.put(AvgProjection, new ProjectionHandler<AvgProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, AvgProjection projection) {
+                return addProjectionToGroupBy(projectObject, groupBy, projection, AVERAGE_OPERATOR, 'avg_')
+            }
+        })
+        groupByProjectionHandlers.put(CountProjection, new ProjectionHandler<CountProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, CountProjection projection) {
+                projectObject.put(MongoEntityPersister.MONGO_ID_FIELD, 1)
+                String projectionKey = 'count'
+                groupBy.put(projectionKey, new Document(SUM_OPERATOR, 1))
+                return projectionKey
+            }
+        })
+        groupByProjectionHandlers.put(CountDistinctProjection, new ProjectionHandler<CountDistinctProjection>() {
+            // equivalent of "select count (distinct fieldName) from someTable". Example:
+            // db.someCollection.aggregate([{ $group: { _id: "$fieldName"}  },{ $group: { _id: 1, count: { $sum: 1 } } } ])
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, CountDistinctProjection projection) {
+                projectObject.put(projection.getPropertyName(), 1)
+                String property = projection.getPropertyName()
+                String projectionValueKey = 'countDistinct_' + property
+                Document id = getIdObjectForGroupBy(groupBy)
+                id.put(projectionValueKey, '$' + property)
+                return projectionValueKey
+            }
+        })
+
+        groupByProjectionHandlers.put(MinProjection, new ProjectionHandler<MinProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, MinProjection projection) {
+                return addProjectionToGroupBy(projectObject, groupBy, projection, MIN_OPERATOR, 'min_')
+            }
+        })
+        groupByProjectionHandlers.put(MaxProjection, new ProjectionHandler<MaxProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, MaxProjection projection) {
+                return addProjectionToGroupBy(projectObject, groupBy, projection, MAX_OPERATOR, 'max_')
+            }
+        })
+        groupByProjectionHandlers.put(SumProjection, new ProjectionHandler<SumProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, SumProjection projection) {
+                return addProjectionToGroupBy(projectObject, groupBy, projection, SUM_OPERATOR, 'sum_')
+            }
+        })
+
+        projectProjectionHandlers.put(DistinctPropertyProjection, new ProjectionHandler<DistinctPropertyProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, DistinctPropertyProjection projection) {
+                String property = projection.getPropertyName()
+                projectObject.put(property, 1)
+                Document id = getIdObjectForGroupBy(groupBy)
+                String projectedValueKey = property.replace('.', '_')
+                id.put(projectedValueKey, '$' + property)
+                return projectedValueKey
+            }
+        })
+
+        projectProjectionHandlers.put(PropertyProjection, new ProjectionHandler<PropertyProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, PropertyProjection projection) {
+                String property = projection.getPropertyName()
+                projectObject.put(property, 1)
+                Document id = getIdObjectForGroupBy(groupBy)
+                String projectedValueKey = property.replace('.', '_')
+                id.put(projectedValueKey, '$' + property)
+                // we add the id to the grouping to make it not distinct
+                id.put(MongoEntityPersister.MONGO_ID_FIELD, '$' + MongoEntityPersister.MONGO_ID_FIELD)
+                return projectedValueKey
+            }
+        })
+
+        projectProjectionHandlers.put(IdProjection, new ProjectionHandler<IdProjection>() {
+            @Override
+            String handle(PersistentEntity entity, Document projectObject, Document groupBy, IdProjection projection) {
+                projectObject.put(MongoEntityPersister.MONGO_ID_FIELD, 1)
+                Document id = getIdObjectForGroupBy(groupBy)
+                id.put(MongoEntityPersister.MONGO_ID_FIELD, '$_id')
+
+                return MongoEntityPersister.MONGO_ID_FIELD
+            }
+        })
+    }
+
+    @PackageScope
+    static Document getIdObjectForGroupBy(Document groupBy) {
+        Object value = groupBy.get(MongoEntityPersister.MONGO_ID_FIELD)
+        Document id
+        if (value instanceof Document) {
+            id = (Document) value
+        } else {
+            id = new Document()
+            groupBy.put(MongoEntityPersister.MONGO_ID_FIELD, id)
+        }
+        return id
+    }
+
+    @PackageScope
+    static String addProjectionToGroupBy(Document projectObject, Document groupBy, PropertyProjection projection, String operator, String prefix) {
+        projectObject.put(projection.getPropertyName(), 1)
+        String property = projection.getPropertyName()
+        String projectionValueKey = prefix + property.replace('.', '_')
+        Document averageProjection = new Document(operator, '$' + property)
+        groupBy.put(projectionValueKey, averageProjection)
+        return projectionValueKey
+    }
+
+    private final AbstractMongoSession mongoSession
+    private final EntityPersister mongoEntityPersister
+    private final ManualProjections manualProjections
+    private boolean isCodecPersister = false
+
+    MongoQuery(AbstractMongoSession session, PersistentEntity entity) {
+        super(session, entity)
+        this.mongoSession = session
+        this.manualProjections = new ManualProjections(entity)
+        if (session != null) {
+            this.mongoEntityPersister = (EntityPersister) session.getPersister(entity)
+            if (this.mongoEntityPersister instanceof MongoCodecEntityPersister) {
+                this.isCodecPersister = true
+            }
+        } else {
+            mongoEntityPersister = null
+        }
+    }
+
+    @Override
+    protected void flushBeforeQuery() {
+        // with Mongo we only flush the session if a transaction is not active to allow for session-managed transactions
+        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+            super.flushBeforeQuery()
+        }
+    }
+
+    /**
+     * Gets the Mongo query for this query instance
+     *
+     * @return The Mongo query
+     */
+    Document getMongoQuery() {
+        Document query = createQueryObject(entity)
+        populateMongoQuery((AbstractMongoSession) getSession(), query, criteria, entity)
+        return query
+    }
+
+    @Override
+    protected List executeQuery(final PersistentEntity entity, final Junction criteria) {
+        final AbstractMongoSession mongoSession = this.mongoSession
+        com.mongodb.client.MongoCollection<Document> collection = mongoSession.getCollection(entity)
+
+        final List<Projection> projectionList = projections().getProjectionList()
+        boolean hasOnlyDistinct = projectionList.size() == 1 && (projectionList.get(0) instanceof DistinctProjection)
+        if (uniqueResult && (projectionList.isEmpty() || hasOnlyDistinct)) {
+            if (isCodecPersister) {
+                collection = (com.mongodb.client.MongoCollection<Document>) (com.mongodb.client.MongoCollection) collection
+                        .withDocumentClass(entity.getJavaClass())
+            }
+            Object dbObject
+            if (criteria.isEmpty()) {
+                FindIterable<Document> cursor = mongoSession.find(collection, createQueryObject(entity))
+
+                // Raw on purpose: with the codec persister the collection was re-typed to the entity
+                // class above, so first() yields an entity, and a FindIterable<Document> here would
+                // make the static compiler insert a cast to Document that fails at runtime.
+                dbObject = ((FindIterable) setHint(cursor)).limit(1)
+                        .first()
+            } else {
+                FindIterable<Document> cursor = mongoSession.find(collection, getMongoQuery())
+
+                // Raw on purpose: with the codec persister the collection was re-typed to the entity
+                // class above, so first() yields an entity, and a FindIterable<Document> here would
+                // make the static compiler insert a cast to Document that fails at runtime.
+                dbObject = ((FindIterable) setHint(cursor)).limit(1)
+                        .first()
+            }
+            if (dbObject == null) {
+                return wrapObjectResultInList(dbObject)
+            }
+            if (isCodecPersister) {
+                if (!mongoSession.contains(dbObject)) {
+                    final EntityAccess entityAccess = mongoSession.createEntityAccess(entity, dbObject)
+                    mongoEntityPersister.firePostLoadEvent(entity, entityAccess)
+                    mongoSession.cacheInstance(dbObject.getClass(), (Serializable) entityAccess.getIdentifier(), dbObject)
+                }
+                return wrapObjectResultInList(dbObject)
+            }
+            return wrapObjectResultInList(createObjectFromDBObject((Document) dbObject))
+        }
+
+        MongoCursor<Document> cursor
+        Document query = createQueryObject(entity)
+
+        if (projectionList.isEmpty() || hasOnlyDistinct) {
+            if (isCodecPersister) {
+                collection = (com.mongodb.client.MongoCollection<Document>) (com.mongodb.client.MongoCollection) collection
+                        .withDocumentClass(entity.getJavaClass())
+                        .withCodecRegistry(mongoSession.getDatastore().getCodecRegistry())
+            }
+            cursor = executeQuery(entity, criteria, collection, query)
+            return new MongoResultList(cursor, offset, mongoEntityPersister)
+        }
+
+        populateMongoQuery((AbstractMongoSession) session, query, criteria, entity)
+        AggregatePipeline aggregatePipeline = buildAggregatePipeline(entity, query, projectionList)
+        List<Document> aggregationPipeline = aggregatePipeline.getAggregationPipeline()
+        boolean singleResult = aggregatePipeline.isSingleResult()
+        List<ProjectedProperty> projectedKeys = aggregatePipeline.getProjectedKeys()
+        List projectedResults = new ArrayList()
+
+        AggregateIterable<Document> aggregatedResults = mongoSession.aggregate(collection, aggregationPipeline)
+        aggregatedResults = (AggregateIterable<Document>) setHint(aggregatedResults)
+        final MongoCursor<Document> aggregateCursor = aggregatedResults.iterator()
+
+        if (singleResult && aggregateCursor.hasNext()) {
+            Document dbo = aggregateCursor.next()
+            for (ProjectedProperty projectedProperty : projectedKeys) {
+                Object value = dbo.get(projectedProperty.projectionKey)
+                PersistentProperty property = projectedProperty.property
+                if (value != null) {
+                    if (property instanceof ToOne) {
+                        projectedResults.add(session.retrieve(property.getType(), (Serializable) value))
+                    } else {
+                        projectedResults.add(value)
+                    }
+                } else {
+                    if (projectedProperty.projection instanceof CountProjection) {
+                        projectedResults.add(0)
+                    }
+                }
+            }
+        } else {
+            return new AggregatedResultList((AbstractMongoSession) getSession(), aggregateCursor, projectedKeys)
+        }
+
+        return projectedResults
+    }
+
+    protected AggregatePipeline buildAggregatePipeline(PersistentEntity entity, Document query, List<Projection> projectionList) {
+        return new AggregatePipeline(this, entity, query, projectionList).build()
+    }
+
+    protected MongoCursor<Document> executeQuery(final PersistentEntity entity,
+                                                 final Junction criteria,
+                                                 final com.mongodb.client.MongoCollection<Document> collection,
+                                                 final Document query) {
+        FindIterable<Document> cursor
+        if (criteria.isEmpty()) {
+            cursor = executeQueryAndApplyPagination(collection, query)
+        } else {
+            populateMongoQuery((AbstractMongoSession) session, query, criteria, entity)
+            cursor = executeQueryAndApplyPagination(collection, query)
+        }
+
+        cursor = (FindIterable<Document>) setHint(cursor)
+
+        return cursor.iterator()
+    }
+
+    private MongoIterable<Document> setHint(MongoIterable<Document> cursor) {
+        MongoIterable<Document> result = cursor
+
+        if (queryArguments != null) {
+            if (queryArguments.containsKey(HINT_ARGUMENT)) {
+                Object hint = queryArguments.get(HINT_ARGUMENT)
+                if (hint instanceof Map) {
+                    if (cursor instanceof FindIterable) {
+                        result = ((FindIterable<Document>) cursor).hint(new Document((Map<String, Object>) hint))
+                    } else if (cursor instanceof AggregateIterable) {
+                        result = ((AggregateIterable<Document>) cursor).hint(new Document((Map<String, Object>) hint))
+                    }
+                } else {
+                    if (cursor instanceof FindIterable) {
+                        result = ((FindIterable<Document>) cursor).hintString(hint.toString())
+                    } else if (cursor instanceof AggregateIterable) {
+                        result = ((AggregateIterable<Document>) cursor).hintString(hint.toString())
+                    }
+                }
+            }
+        }
+
+        return result
+    }
+
+    protected FindIterable<Document> executeQueryAndApplyPagination(com.mongodb.client.MongoCollection<Document> collection, Document query) {
+        com.mongodb.client.MongoCollection<Document> target = collection
+        Object readConcernObject = queryArguments.get(READ_CONCERN_ARGUMENT)
+        if (readConcernObject instanceof ReadConcern) {
+            target = target.withReadConcern(
+                    (ReadConcern) readConcernObject
+            )
+        }
+
+        final FindIterable<Document> iterable = mongoSession.find(target, query)
+        if (offset != null && offset > 0) {
+            iterable.skip(offset)
+        }
+        if (max != null && max > -1) {
+            iterable.limit(max)
+        }
+        if (uniqueResult) {
+            iterable.limit(1)
+        }
+
+        if (!orderBy.isEmpty()) {
+            Document orderObject = new Document()
+            for (Order order : orderBy) {
+                String property = order.getProperty()
+                property = getPropertyName(entity, property)
+                orderObject.put(property, order.getDirection() == Order.Direction.DESC ? -1 : 1)
+            }
+            iterable.sort(orderObject)
+        } else {
+            MongoCollection coll = (MongoCollection) entity.getMapping().getMappedForm()
+            if (coll != null && coll.getSort() != null) {
+                Document orderObject = new Document()
+                Order order = (Order) coll.getSort()
+                String property = order.getProperty()
+                property = getPropertyName(entity, property)
+                orderObject.put(property, order.getDirection() == Order.Direction.DESC ? -1 : 1)
+                iterable.sort(orderObject)
+            }
+        }
+
+        return iterable
+    }
+
+    private Document getClassFieldDocument(final PersistentEntity entity) {
+        Object classFieldValue
+        Collection<PersistentEntity> childEntities = entity.getMappingContext().getChildEntities(entity)
+        if (childEntities.size() > 0) {
+            HashMap classValue = new HashMap()
+            ArrayList classes = new ArrayList()
+            classes.add(entity.getDiscriminator())
+            for (PersistentEntity childEntity : childEntities) {
+                classes.add(childEntity.getDiscriminator())
+            }
+            classValue.put(MONGO_IN_OPERATOR, classes)
+            classFieldValue = classValue
+        } else {
+            classFieldValue = entity.getDiscriminator()
+        }
+        return new Document(MongoEntityPersister.MONGO_CLASS_FIELD, classFieldValue)
+    }
+
+    protected Document createQueryObject(PersistentEntity persistentEntity) {
+        Document query
+        if (persistentEntity.isRoot()) {
+            query = new Document()
+        } else {
+            query = getClassFieldDocument(persistentEntity)
+        }
+        return query
+    }
+
+    static void populateMongoQuery(final AbstractMongoSession session, Document query, Junction criteria, final PersistentEntity entity) {
+        EmbeddedQueryEncoder queryEncoder
+        if (session instanceof MongoCodecSession) {
+            final MongoDatastore datastore = session.getDatastore()
+            final CodecRegistry codecRegistry = datastore.getCodecRegistry()
+            queryEncoder = new EmbeddedQueryEncoder() {
+                @Override
+                Object encode(Embedded embedded, Object instance) {
+                    final PersistentEntityCodec codec = (PersistentEntityCodec) codecRegistry.get(embedded.getType())
+                    final BsonDocument doc = new BsonDocument()
+                    codec.encode(new BsonDocumentWriter(doc), instance, ENCODER_CONTEXT, false)
+                    return doc
+                }
+            }
+        } else {
+            queryEncoder = new EmbeddedQueryEncoder() {
+                @Override
+                Object encode(Embedded embedded, Object instance) {
+                    MongoEntityPersister persister = (MongoEntityPersister) session.getPersister(entity.getJavaClass())
+                    return persister.createNativeObjectForEmbedded(embedded, instance)
+                }
+            }
+        }
+
+        populateMongoQuery(queryEncoder, query, criteria, entity)
+    }
+
+    @SuppressWarnings('unchecked')
+    static void populateMongoQuery(final EmbeddedQueryEncoder queryEncoder, Document query, Junction criteria, final PersistentEntity entity) {
+        List subList = null
+        // if a query combines more than 1 item, wrap the items in individual $and or $or arguments
+        // so that property names can't clash (e.g. for an $and containing two $ors)
+        if (criteria.getCriteria().size() > 1) {
+            if (criteria instanceof Disjunction) {
+                subList = new ArrayList()
+                query.put(OR_OPERATOR, subList)
+            } else if (criteria instanceof Conjunction) {
+                subList = new ArrayList()
+                query.put(AND_OPERATOR, subList)
+            }
+        }
+        for (Criterion criterion : criteria.getCriteria()) {
+            final QueryHandler queryHandler = queryHandlers.get(criterion.getClass())
+            if (queryHandler != null) {
+                Document dbo = query
+                if (subList != null) {
+                    dbo = new Document()
+                    subList.add(dbo)
+                }
+
+                if (criterion instanceof PropertyCriterion && !(criterion instanceof GeoCriterion)) {
+                    PropertyCriterion pc = (PropertyCriterion) criterion
+                    PersistentProperty property = entity.getPropertyByName(pc.getProperty())
+                    if (property instanceof Custom) {
+                        CustomTypeMarshaller<Object, Object, Document> customTypeMarshaller = (CustomTypeMarshaller<Object, Object, Document>) ((Custom) property).getCustomTypeMarshaller()
+                        if (!(customTypeMarshaller instanceof CodecCustomTypeMarshaller)) {
+                            customTypeMarshaller.query(property, pc, query)
+                            continue
+                        }
+                    }
+                }
+                queryHandler.handle(queryEncoder, criterion, dbo, entity)
+            } else {
+                throw new InvalidDataAccessResourceUsageException('Queries of type ' + criterion.getClass().getSimpleName() + ' are not supported by this implementation')
+            }
+        }
+    }
+
+    /**
+     * Rewrites a query built against an embedded entity so it applies to the owning document,
+     * qualifying each property name with the embedded property's path. Logical operators such as
+     * {@code $and} and {@code $or} must stay at the current level (a key like {@code extRef1.$and}
+     * matches nothing), so their nested documents are rewritten recursively instead.
+     */
+    @PackageScope
+    static void prefixEmbeddedQuery(String prefix, Document source, Document target) {
+        for (String key : source.keySet()) {
+            Object value = source.get(key)
+            if (key.charAt(0) == ('$' as char)) {
+                if (!(value instanceof List)) {
+                    // A top-level operator whose value is not a rewritable list of clauses
+                    // (e.g. $where from a property-to-property comparison, or $text) cannot be
+                    // qualified with the embedded path - prefixing it would silently match nothing.
+                    throw new UnsupportedOperationException('Criterion [' + key +
+                            '] is not supported inside an embedded association query')
+                }
+                List<Object> rewritten = new ArrayList<>()
+                for (Object element : (List) value) {
+                    if (element instanceof Document) {
+                        Document rewrittenElement = new Document()
+                        prefixEmbeddedQuery(prefix, (Document) element, rewrittenElement)
+                        rewritten.add(rewrittenElement)
+                    } else {
+                        rewritten.add(element)
+                    }
+                }
+                target.put(key, rewritten)
+            } else {
+                target.put(prefix + '.' + key, value)
+            }
+        }
+    }
+
+    private Object createObjectFromDBObject(Document dbObject) {
+        // we always use the session cached version where available.
+        final Object id = dbObject.get(MongoEntityPersister.MONGO_ID_FIELD)
+        Class type = mongoEntityPersister.getPersistentEntity().getJavaClass()
+        Object instance = mongoSession.getCachedInstance(type, (Serializable) id)
+        if (instance == null) {
+            instance = ((MongoEntityPersister) mongoEntityPersister).createObjectFromNativeEntry(
+                    mongoEntityPersister.getPersistentEntity(), (Serializable) id, dbObject)
+            mongoSession.cacheInstance(type, (Serializable) id, instance)
+        }
+        // note cached instances may be stale, but user can call 'refresh' to fix that.
+        return instance
+    }
+
+    private List wrapObjectResultInList(Object object) {
+        List result = new ArrayList()
+        result.add(object)
+        return result
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query near(String property, List value) {
+        add(new Near(property, value))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query near(String property, Point value) {
+        add(new Near(property, value))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query near(String property, List value, Distance maxDistance) {
+        add(new Near(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query near(String property, Point value, Distance maxDistance) {
+        add(new Near(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query near(String property, List value, Number maxDistance) {
+        add(new Near(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query near(String property, Point value, Number maxDistance) {
+        add(new Near(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query nearSphere(String property, List value) {
+        add(new NearSphere(property, value))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query nearSphere(String property, Point value) {
+        add(new NearSphere(property, value))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query nearSphere(String property, List value, Distance maxDistance) {
+        add(new NearSphere(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query nearSphere(String property, Point value, Distance maxDistance) {
+        add(new NearSphere(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query nearSphere(String property, List value, Number maxDistance) {
+        add(new NearSphere(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values near the given two dimensional list
+     *
+     * @param property The property
+     * @param value    A two dimensional list of values
+     * @return this
+     */
+    Query nearSphere(String property, Point value, Number maxDistance) {
+        add(new NearSphere(property, value, maxDistance))
+        return this
+    }
+
+    /**
+     * Geospacial query for values within a given box. A box is defined as a multi-dimensional list in the form
+     * [[40.73083, -73.99756], [40.741404,  -73.988135]]
+     *
+     * @param property The property
+     * @param value    A multi-dimensional list of values
+     * @return This query
+     */
+    Query withinBox(String property, List value) {
+        add(new WithinBox(property, value))
+        return this
+    }
+
+    /**
+     * Geospacial query for values within the given shape
+     *
+     * @param property The property
+     * @param shape    The shape
+     * @return The query instance
+     */
+    Query geoWithin(String property, Shape shape) {
+        add(new GeoWithin(property, shape))
+        return this
+    }
+
+    /**
+     * Geospacial query for values within the given shape
+     *
+     * @param property The property
+     * @param shape    The shape
+     * @return The query instance
+     */
+    Query geoIntersects(String property, GeoJSON shape) {
+        add(new GeoIntersects(property, shape))
+        return this
+    }
+
+    /**
+     * Geospacial query for values within a given polygon. A polygon is defined as a multi-dimensional list in the form
+     * [[0, 0], [3, 6], [6, 0]]
+     *
+     * @param property The property
+     * @param value    A multi-dimensional list of values
+     * @return This query
+     */
+    Query withinPolygon(String property, List value) {
+        add(new WithinPolygon(property, value))
+        return this
+    }
+
+    /**
+     * Geospacial query for values within a given circle. A circle is defined as a multi-dimensial list containing the position of the center and the radius:
+     * [[50, 50], 10]
+     *
+     * @param property The property
+     * @param value    A multi-dimensional list of values
+     * @return This query
+     */
+    Query withinCircle(String property, List value) {
+        add(new WithinBox(property, value))
+        return this
+    }
+
+    /**
+     * @param arguments The query arguments
+     */
+    void setArguments(Map arguments) {
+        this.queryArguments = arguments
+    }
+
+    /**
+     * Used for Geospacial querying
+     *
+     * @author Graeme Rocher
+     * @since 1.0
+     */
+    static class Near extends GeoCriterion {
+
+        Distance maxDistance = null
+
+        Near(String name, Object value) {
+            super(name, value)
+        }
+
+        Near(String name, Object value, Distance maxDistance) {
+            super(name, value)
+            this.maxDistance = maxDistance
+        }
+
+        Near(String name, Object value, Number maxDistance) {
+            super(name, value)
+            this.maxDistance = Distance.valueOf(maxDistance.doubleValue())
+        }
+
+        void setMaxDistance(Distance maxDistance) {
+            this.maxDistance = maxDistance
+        }
+
+    }
+
+    /**
+     * Used for Geospacial querying with the $nearSphere operator
+     *
+     * @author Graeme Rocher
+     * @since 1.0
+     */
+    static class NearSphere extends Near {
+
+        NearSphere(String name, Object value) {
+            super(name, value)
+        }
+
+        NearSphere(String name, Object value, Distance maxDistance) {
+            super(name, value, maxDistance)
+        }
+
+        NearSphere(String name, Object value, Number maxDistance) {
+            super(name, value, maxDistance)
+        }
+
+    }
+
+    /**
+     * Used for Geospacial querying of boxes
+     *
+     * @author Graeme Rocher
+     * @since 1.0
+     */
+    static class WithinBox extends PropertyCriterion {
+
+        WithinBox(String name, List value) {
+            super(name, value)
+        }
+
+        List getValues() {
+            return (List) getValue()
+        }
+
+        void setValue(List matrix) {
+            // Direct field access: through property syntax Groovy would route the assignment back
+            // into this setter.
+            this.@value = matrix
+        }
+
+    }
+
+    /**
+     * Used for Geospacial querying of polygons
+     */
+    static class WithinPolygon extends PropertyCriterion {
+
+        WithinPolygon(String name, List value) {
+            super(name, value)
+        }
+
+        List getValues() {
+            return (List) getValue()
+        }
+
+        void setValue(List value) {
+            // Direct field access: through property syntax Groovy would route the assignment back
+            // into this setter.
+            this.@value = value
+        }
+
+    }
+
+    /**
+     * Used for Geospacial querying of circles
+     *
+     * @author Graeme Rocher
+     * @since 1.0
+     */
+    static class WithinCircle extends PropertyCriterion {
+
+        WithinCircle(String name, List value) {
+            super(name, value)
+        }
+
+        List getValues() {
+            return (List) getValue()
+        }
+
+        void setValue(List matrix) {
+            // Direct field access: through property syntax Groovy would route the assignment back
+            // into this setter.
+            this.@value = matrix
+        }
+
+    }
+
+    /**
+     * Used for all GeoSpacial queries using 2dsphere indexes
+     */
+    static class GeoCriterion extends PropertyCriterion {
+
+        GeoCriterion(String name, Object value) {
+            super(name, value)
+        }
+
+    }
+
+    static class GeoWithin extends GeoCriterion {
+
+        GeoWithin(String name, Object value) {
+            super(name, value)
+        }
+
+    }
+
+    static class GeoIntersects extends GeoCriterion {
+
+        GeoIntersects(String name, Object value) {
+            super(name, value)
+        }
+
+    }
+
+    static class AggregatedResultList extends AbstractList implements Closeable {
+
+        @PackageScope MongoCursor cursor
+        private List<ProjectedProperty> projectedProperties
+        private List initializedObjects = new ArrayList()
+        private int internalIndex = 0
+        @PackageScope boolean initialized = false
+        private boolean containsAssociations = false
+        private AbstractMongoSession session
+
+        AggregatedResultList(AbstractMongoSession session, MongoCursor<Document> cursor, List<ProjectedProperty> projectedProperties) {
+            this.cursor = cursor
+            this.projectedProperties = projectedProperties
+            this.session = session
+            for (ProjectedProperty projectedProperty : projectedProperties) {
+                if (projectedProperty.property instanceof Association) {
+                    this.containsAssociations = true
+                    break
+                }
+            }
+        }
+
+        @Override
+        String toString() {
+            return initializedObjects.toString()
+        }
+
+        @Override
+        Object get(int index) {
+            if (containsAssociations) {
+                initializeFully()
+            }
+            if (initializedObjects.size() > index) {
+                return initializedObjects.get(index)
+            } else if (!initialized) {
+                boolean hasResults = false
+                while (cursor.hasNext()) {
+                    hasResults = true
+                    Document dbo = (Document) cursor.next()
+                    Object projected = addInitializedObject(dbo)
+                    if (index == internalIndex) {
+                        return projected
+                    }
+                }
+                if (!hasResults) {
+                    handleNoResults()
+                }
+                initialized = true
+            }
+            throw new ArrayIndexOutOfBoundsException('Index value ' + index + ' exceeds size of aggregate list')
+        }
+
+        @Override
+        Object set(int index, Object element) {
+            initializeFully()
+            return initializedObjects.set(index, element)
+        }
+
+        @Override
+        ListIterator listIterator() {
+            return listIterator(0)
+        }
+
+        @Override
+        ListIterator listIterator(int index) {
+            initializeFully()
+            return initializedObjects.listIterator(index)
+        }
+
+        protected void initializeFully() {
+            if (initialized) {
+                return
+            }
+            if (containsAssociations) {
+                if (projectedProperties.size() == 1) {
+                    ProjectedProperty projectedProperty = projectedProperties.get(0)
+                    PersistentProperty property = projectedProperty.property
+                    List<Serializable> identifiers = new ArrayList<>()
+                    boolean hasResults = false
+                    while (cursor.hasNext()) {
+                        hasResults = true
+                        Document dbo = (Document) cursor.next()
+                        Object id = getProjectedValue(dbo, projectedProperty.projectionKey)
+                        identifiers.add((Serializable) id)
+                    }
+                    if (!hasResults) {
+                        handleNoResults()
+                    } else if (property instanceof Embedded) {
+                        Embedded embedded = (Embedded) property
+                        List embeddedList = new ArrayList()
+                        CodecRegistry codecRegistry = session.getDatastore().getCodecRegistry()
+                        PersistentEntityCodec codec = new PersistentEntityCodec(codecRegistry, embedded.getAssociatedEntity())
+
+                        for (Serializable embeddedDoc : identifiers) {
+                            if (embeddedDoc instanceof Document) {
+                                Document documentObject = (Document) embeddedDoc
+
+                                Object decoded = codec.decode(new BsonDocumentReader(documentObject.toBsonDocument(Document, codecRegistry)))
+                                embeddedList.add(
+                                        decoded
+                                )
+                            }
+                        }
+
+                        this.initializedObjects = embeddedList
+                    } else {
+                        this.initializedObjects = session.retrieveAll(property.getType(), identifiers)
+                    }
+                } else {
+                    Map<Integer, Map<Class, List<Serializable>>> associationMap = createAssociationMap()
+
+                    boolean hasResults = false
+                    while (cursor.hasNext()) {
+                        hasResults = true
+                        Document dbo = (Document) cursor.next()
+                        List<Object> projectedResult = new ArrayList<>()
+                        int index = 0
+                        for (ProjectedProperty projectedProperty : projectedProperties) {
+                            PersistentProperty property = projectedProperty.property
+                            Object value = getProjectedValue(dbo, projectedProperty.projectionKey)
+                            if (property instanceof Association) {
+                                if ((!(property instanceof Embedded) && !(property instanceof EmbeddedCollection) && !(property instanceof Basic))) {
+                                    Map<Class, List<Serializable>> identifierMap = associationMap.get(index)
+                                    Class type = ((Association) property).getAssociatedEntity().getJavaClass()
+                                    identifierMap.get(type).add((Serializable) value)
+                                }
+                            }
+                            projectedResult.add(value)
+                            index++
+                        }
+
+                        initializedObjects.add(projectedResult)
+                    }
+
+                    if (!hasResults) {
+                        handleNoResults()
+                        return
+                    }
+
+                    Map<Integer, List> finalResults = new HashMap<>()
+                    for (Integer index : associationMap.keySet()) {
+                        Map<Class, List<Serializable>> associatedEntityIdentifiers = associationMap.get(index)
+                        for (Class associationClass : associatedEntityIdentifiers.keySet()) {
+                            List<Serializable> identifiers = associatedEntityIdentifiers.get(associationClass)
+                            finalResults.put(index, session.retrieveAll(associationClass, identifiers))
+                        }
+                    }
+
+                    for (Object initializedObject : initializedObjects) {
+                        List projected = (List) initializedObject
+                        for (Integer index : finalResults.keySet()) {
+                            List resultsByIndex = finalResults.get(index)
+                            if (index < resultsByIndex.size()) {
+                                projected.set(index, resultsByIndex.get(index))
+                            } else {
+                                projected.set(index, null)
+                            }
+                        }
+                    }
+                }
+            } else {
+                boolean hasResults = false
+                while (cursor.hasNext()) {
+                    hasResults = true
+                    Document dbo = (Document) cursor.next()
+                    addInitializedObject(dbo)
+                }
+                if (!hasResults) {
+                    handleNoResults()
+                }
+            }
+            initialized = true
+        }
+
+        protected void handleNoResults() {
+            ProjectedProperty projectedProperty = projectedProperties.get(0)
+            if (projectedProperty.projection instanceof CountProjection) {
+                initializedObjects.add(0)
+            }
+        }
+
+        private Map<Integer, Map<Class, List<Serializable>>> createAssociationMap() {
+            Map<Integer, Map<Class, List<Serializable>>> associationMap = new HashMap<>()
+            associationMap = associationMap.withDefault { Object o ->
+                Map<Class, List<Serializable>> subMap = new HashMap<>()
+                subMap = subMap.withDefault { Object k ->
+                    return new ArrayList<Serializable>()
+                }
+                return subMap
+            }
+            return associationMap
+        }
+
+        @Override
+        Iterator iterator() {
+            if (initialized || containsAssociations || internalIndex > 0) {
+                initializeFully()
+                return initializedObjects.iterator()
+            }
+
+            if (!cursor.hasNext()) {
+                handleNoResults()
+                return initializedObjects.iterator()
+            }
+
+            return new Iterator() {
+                @Override
+                boolean hasNext() {
+                    boolean hasMore = cursor.hasNext()
+                    if (!hasMore) {
+                        initialized = true
+                    }
+                    return hasMore
+                }
+
+                @Override
+                Object next() {
+                    Document dbo = (Document) cursor.next()
+                    return addInitializedObject(dbo)
+                }
+
+                @Override
+                void remove() {
+                    throw new UnsupportedOperationException('Aggregate result list cannot be mutated.')
+                }
+            }
+        }
+
+        @PackageScope
+        Object addInitializedObject(Document dbo) {
+            if (projectedProperties.size() > 1) {
+                List<Object> projected = new ArrayList<>()
+                for (ProjectedProperty projectedProperty : projectedProperties) {
+                    Object value
+                    value = getProjectedValue(dbo, projectedProperty.projectionKey)
+                    projected.add(value)
+                }
+                initializedObjects.add(internalIndex, projected)
+                internalIndex++
+                return projected
+            }
+            ProjectedProperty projectedProperty = projectedProperties.get(0)
+            Object projected = getProjectedValue(dbo, projectedProperty.projectionKey)
+            initializedObjects.add(internalIndex, projected)
+            internalIndex++
+            return projected
+        }
+
+        private Object getProjectedValue(Document dbo, String projectionKey) {
+            Object value
+            if (projectionKey.startsWith('id.')) {
+                String key = projectionKey.substring(3)
+                Document id = (Document) dbo.get(MongoEntityPersister.MONGO_ID_FIELD)
+                value = id.get(key)
+            } else {
+                value = dbo.get(projectionKey)
+            }
+            return value
+        }
+
+        @Override
+        int size() {
+            initializeFully()
+            return initializedObjects.size()
+        }
+
+        @Override
+        void close() throws IOException {
+            cursor.close()
+        }
+
+    }
+
+    @SuppressWarnings('serial')
+    static class MongoResultList extends AbstractResultList {
+
+        private EntityPersister mongoEntityPersister
+        private MongoCursor cursor
+        private boolean isCodecPersister
+
+        MongoResultList(MongoCursor cursor, Integer offset, EntityPersister mongoEntityPersister) {
+            super(offset == null ? 0 : offset, cursor)
+            this.cursor = cursor
+            this.mongoEntityPersister = mongoEntityPersister
+            this.isCodecPersister = mongoEntityPersister instanceof MongoCodecEntityPersister
+        }
+
+        @Override
+        void close() throws IOException {
+            cursor.close()
+        }
+
+        @Override
+        String toString() {
+            initializeFully()
+            return initializedObjects.toString()
+        }
+
+        /**
+         * @return The underlying MongoDB cursor instance
+         */
+        MongoCursor getCursor() {
+            return cursor
+        }
+
+        @Override
+        protected Object nextDecoded() {
+            final Object o = cursor.next()
+            if (isCodecPersister) {
+                final AbstractMongoSession session = (AbstractMongoSession) mongoEntityPersister.getSession()
+                if (!session.contains(o)) {
+                    final PersistentEntity entity = mongoEntityPersister.getPersistentEntity()
+                    final EntityAccess entityAccess = session.createEntityAccess(entity, o)
+                    final Object id = entityAccess.getIdentifier()
+                    if (id != null) {
+                        session.cacheInstance(entity.getJavaClass(), (Serializable) id, o)
+                    }
+                    mongoEntityPersister.firePostLoadEvent(entity, entityAccess)
+                }
+            }
+            return o
+        }
+
+        @Override
+        protected Object convertObject(Object object) {
+            return isCodecPersister ? object : convertDBObject(object)
+        }
+
+        protected Object convertDBObject(Object object) {
+            if (mongoEntityPersister instanceof MongoCodecEntityPersister) {
+                return object
+            }
+            final Document dbObject = (Document) object
+            Object id = dbObject.get(MongoEntityPersister.MONGO_ID_FIELD)
+            SessionImplementor session = (SessionImplementor) mongoEntityPersister.getSession()
+            Class type = mongoEntityPersister.getPersistentEntity().getJavaClass()
+            Object instance = session.getCachedInstance(type, (Serializable) id)
+            if (instance == null) {
+                final MongoEntityPersister mep = (MongoEntityPersister) this.mongoEntityPersister
+                instance = mep.createObjectFromNativeEntry(
+                        this.mongoEntityPersister.getPersistentEntity(), (Serializable) id, dbObject)
+                session.cacheInstance(type, (Serializable) id, instance)
+            }
+            return instance
+        }
+
+    }
+
+    static class ProjectedProperty {
+
+        public Projection projection
+        public String projectionKey
+        public PersistentProperty property
+
+    }
+
+    protected static class AggregatePipeline {
+
+        private PersistentEntity entity
+        private Document query
+        private List<Projection> projectionList
+        private List<Document> aggregationPipeline
+        private List<ProjectedProperty> projectedKeys
+        private boolean singleResult
+        private final MongoQuery mongoQuery
+
+        AggregatePipeline(MongoQuery mongoQuery, PersistentEntity entity, Document queryObject, List<Projection> projectionList) {
+            this.mongoQuery = mongoQuery
+            this.entity = entity
+            this.query = queryObject
+            this.projectionList = projectionList
+        }
+
+        List<Document> getAggregationPipeline() {
+            return aggregationPipeline
+        }
+
+        List<ProjectedProperty> getProjectedKeys() {
+            return projectedKeys
+        }
+
+        boolean isSingleResult() {
+            return singleResult
+        }
+
+        AggregatePipeline build() {
+            aggregationPipeline = new ArrayList<Document>()
+
+            if (!query.keySet().isEmpty()) {
+                aggregationPipeline.add(new Document(MATCH_OPERATOR, query))
+            }
+
+            projectedKeys = new ArrayList<ProjectedProperty>()
+            singleResult = true
+
+            Document projectObject = new Document()
+
+            Document groupByObject = new Document()
+            groupByObject.put(MongoEntityPersister.MONGO_ID_FIELD, 0)
+            Document additionalGroupBy = null
+
+            for (Projection projection : projectionList) {
+                ProjectionHandler projectionHandler = projectProjectionHandlers.get(projection.getClass())
+                ProjectedProperty projectedProperty = new ProjectedProperty()
+                projectedProperty.projection = projection
+                if (projection instanceof PropertyProjection) {
+                    PropertyProjection propertyProjection = (PropertyProjection) projection
+                    String propertyName = propertyProjection.getPropertyName()
+
+                    PersistentProperty property = entity.getPropertyByName(propertyName)
+                    if (property != null) {
+                        projectedProperty.property = property
+                    } else if (!propertyName.contains('.')) {
+                        throw new InvalidDataAccessResourceUsageException('Attempt to project on a non-existent project [' + propertyName + ']')
+                    }
+                }
+                if (projectionHandler != null) {
+                    singleResult = false
+
+                    String aggregationKey = projectionHandler.handle(entity, projectObject, groupByObject, projection)
+                    aggregationKey = 'id.' + aggregationKey
+                    projectedProperty.projectionKey = aggregationKey
+                    projectedKeys.add(projectedProperty)
+                } else {
+                    projectionHandler = groupByProjectionHandlers.get(projection.getClass())
+                    if (projectionHandler != null) {
+                        projectedProperty.projectionKey = projectionHandler.handle(entity, projectObject, groupByObject, projection)
+                        projectedKeys.add(projectedProperty)
+
+                        if (projection instanceof CountDistinctProjection) {
+                            Document finalCount = new Document(MongoEntityPersister.MONGO_ID_FIELD, 1)
+                            finalCount.put(projectedProperty.projectionKey, new Document(SUM_OPERATOR, 1))
+                            additionalGroupBy = new Document(GROUP_OPERATOR, finalCount)
+                        }
+                    }
+                }
+            }
+
+            if (!projectObject.isEmpty()) {
+                aggregationPipeline.add(new Document(PROJECT_OPERATOR, projectObject))
+            }
+
+            aggregationPipeline.add(new Document(GROUP_OPERATOR, groupByObject))
+
+            if (additionalGroupBy != null) {
+                aggregationPipeline.add(additionalGroupBy)
+            }
+
+            List<Order> orderBy = mongoQuery.getOrderBy()
+            if (!orderBy.isEmpty()) {
+                Document sortBy = new Document()
+                for (Order order : orderBy) {
+                    String prop = order.getProperty()
+                    String sortKey = prop
+                    for (ProjectedProperty pp : projectedKeys) {
+                        if (pp.property != null && pp.property.getName() == prop) {
+                            sortKey = pp.projectionKey
+                            if (sortKey.startsWith('id.')) {
+                                sortKey = MongoEntityPersister.MONGO_ID_FIELD + '.' + sortKey.substring(3)
+                            }
+                            break
+                        }
+                    }
+                    sortBy.put(sortKey, order.getDirection() == Order.Direction.ASC ? 1 : -1)
+                }
+                aggregationPipeline.add(new Document(SORT_OPERATOR, sortBy))
+            }
+
+            int max = mongoQuery.getMax() != null ? mongoQuery.getMax() : -1
+            if (max > 0) {
+                aggregationPipeline.add(new Document('$limit', max))
+            }
+            int offset = mongoQuery.getOffset() != null ? mongoQuery.getOffset() : 0
+            if (offset > 0) {
+                aggregationPipeline.add(new Document('$skip', offset))
+            }
+
+            return this
+        }
+
+    }
+
+}
diff --git a/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java b/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java
deleted file mode 100644
index d00231eb66..0000000000
--- a/grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java
+++ /dev/null
@@ -1,1633 +0,0 @@
-/* Copyright (C) 2010-2025 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.grails.datastore.mapping.mongo.query;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-
-import groovy.lang.Closure;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
-
-import com.mongodb.ReadConcern;
-import com.mongodb.client.AggregateIterable;
-import com.mongodb.client.FindIterable;
-import com.mongodb.client.MongoCursor;
-import com.mongodb.client.MongoIterable;
-import org.bson.BsonDocument;
-import org.bson.BsonDocumentReader;
-import org.bson.BsonDocumentWriter;
-import org.bson.Document;
-import org.bson.codecs.configuration.CodecRegistry;
-
-import org.springframework.dao.InvalidDataAccessResourceUsageException;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-
-import grails.mongodb.geo.Box;
-import grails.mongodb.geo.Circle;
-import grails.mongodb.geo.Distance;
-import grails.mongodb.geo.GeoJSON;
-import grails.mongodb.geo.Point;
-import grails.mongodb.geo.Polygon;
-import grails.mongodb.geo.Shape;
-import grails.mongodb.geo.Sphere;
-import org.grails.datastore.bson.codecs.CodecCustomTypeMarshaller;
-import org.grails.datastore.bson.query.BsonQuery;
-import org.grails.datastore.bson.query.EmbeddedQueryEncoder;
-import org.grails.datastore.gorm.mongo.geo.GeoJSONType;
-import org.grails.datastore.gorm.query.AbstractResultList;
-import org.grails.datastore.mapping.core.SessionImplementor;
-import org.grails.datastore.mapping.engine.EntityAccess;
-import org.grails.datastore.mapping.engine.EntityPersister;
-import org.grails.datastore.mapping.engine.types.CustomTypeMarshaller;
-import org.grails.datastore.mapping.model.EmbeddedPersistentEntity;
-import org.grails.datastore.mapping.model.PersistentEntity;
-import org.grails.datastore.mapping.model.PersistentProperty;
-import org.grails.datastore.mapping.model.types.Association;
-import org.grails.datastore.mapping.model.types.Basic;
-import org.grails.datastore.mapping.model.types.Custom;
-import org.grails.datastore.mapping.model.types.Embedded;
-import org.grails.datastore.mapping.model.types.EmbeddedCollection;
-import org.grails.datastore.mapping.model.types.ToOne;
-import org.grails.datastore.mapping.mongo.AbstractMongoSession;
-import org.grails.datastore.mapping.mongo.MongoCodecSession;
-import org.grails.datastore.mapping.mongo.MongoDatastore;
-import org.grails.datastore.mapping.mongo.config.MongoCollection;
-import org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister;
-import org.grails.datastore.mapping.mongo.engine.MongoEntityPersister;
-import org.grails.datastore.mapping.mongo.engine.MongoIdCoercion;
-import org.grails.datastore.mapping.mongo.engine.codecs.PersistentEntityCodec;
-import org.grails.datastore.mapping.query.AssociationQuery;
-import org.grails.datastore.mapping.query.Query;
-import org.grails.datastore.mapping.query.api.QueryArgumentsAware;
-import org.grails.datastore.mapping.query.projections.ManualProjections;
-
-/**
- * A {@link org.grails.datastore.mapping.query.Query} implementation for the Mongo document store.
- *
- * @author Graeme Rocher
- * @since 1.0
- */
-@SuppressWarnings("rawtypes")
-public class MongoQuery extends BsonQuery implements QueryArgumentsAware {
-
-    public static final String MONGO_IN_OPERATOR = IN_OPERATOR;
-    public static final String MONGO_OR_OPERATOR = OR_OPERATOR;
-    public static final String MONGO_AND_OPERATOR = AND_OPERATOR;
-    public static final String MONGO_GTE_OPERATOR = GTE_OPERATOR;
-    public static final String MONGO_LTE_OPERATOR = LTE_OPERATOR;
-    public static final String MONGO_GT_OPERATOR = GT_OPERATOR;
-    public static final String MONGO_LT_OPERATOR = LT_OPERATOR;
-    public static final String MONGO_NE_OPERATOR = NE_OPERATOR;
-    public static final String MONGO_NIN_OPERATOR = NIN_OPERATOR;
-    public static final String MONGO_REGEX_OPERATOR = REGEX_OPERATOR;
-
-    public static final String MONGO_WHERE_OPERATOR = WHERE_OPERATOR;
-
-    public static final String HINT_ARGUMENT = "hint";
-    public static final String READ_CONCERN_ARGUMENT = "readConcern";
-
-    private Map queryArguments = Collections.emptyMap();
-
-    public static final String NEAR_OPERATOR = "$near";
-
-    public static final String BOX_OPERATOR = "$box";
-
-    public static final String POLYGON_OPERATOR = "$polygon";
-
-    public static final String WITHIN_OPERATOR = "$within";
-
-    public static final String CENTER_OPERATOR = "$center";
-
-    public static final String GEO_WITHIN_OPERATOR = "$geoWithin";
-
-    public static final String GEOMETRY_OPERATOR = "$geometry";
-
-    public static final String CENTER_SPHERE_OPERATOR = "$centerSphere";
-
-    public static final String GEO_INTERSECTS_OPERATOR = "$geoIntersects";
-
-    public static final String MAX_DISTANCE_OPERATOR = "$maxDistance";
-
-    public static final String NEAR_SPHERE_OPERATOR = "$nearSphere";
-
-    static {
-        queryHandlers.put(IdEquals.class, new QueryHandler<IdEquals>() {
-            // Exercised end-to-end by StringIdWithObjectIdStorageSpec:
-            //   - "with storedAs ObjectId, point lookup by hex string works" (happy path)
-            //   - "with storedAs ObjectId, point lookup of a non-hex id matches the
-            //     BSON String the encoder wrote" (null-return fallback for natural keys)
-            //   - "with storedAs ObjectId, updates persist (no phantom OptimisticLockingException)"
-            //     and related update/delete specs (which also hit IdEquals via the session filter)
-            public void handle(EmbeddedQueryEncoder queryEncoder, IdEquals criterion, Document query, PersistentEntity entity) {
-                Object value = criterion.getValue();
-                // Prefer the configured storage type ('storedAs' on the id mapping) so query BSON
-                // matches what's actually on disk. Falls back to the declared Java type otherwise.
-                Class<?> storedAs = MongoIdCoercion.resolveStoredAs(entity);
-                Class<?> targetType = storedAs != null ? storedAs : entity.getIdentity().getType();
-                Object converted = entity.getMappingContext().getConversionService().convert(value, targetType);
-                // Symmetry with IdentityEncoder's non-hex fallback: if the converter returns
-                // null for a non-null input (e.g. a natural-key String being converted to
-                // ObjectId), keep the original value so the query targets what the encoder
-                // actually wrote rather than {_id: null}.
-                if (converted == null && value != null) {
-                    converted = value;
-                }
-                query.put(MongoEntityPersister.MONGO_ID_FIELD, converted);
-            }
-        });
-
-        // Override the In handler so that criteria targeting the identity (findAllByIdInList,
-        // Domain.createCriteria().list { 'in'('id', [...]) }, etc.) honor 'storedAs' the same way
-        // IdEquals does. Without this, a domain declaring storedAs: ObjectId would send BSON Strings
-        // in {_id: {$in: [...]}} and miss all stored ObjectId documents.
-        //
-        // Exercised end-to-end by StringIdWithObjectIdStorageSpec:
-        //   - "with storedAs ObjectId, findAllByIdInList resolves all ids" (happy path via dynamic finder)
-        //   - "with storedAs ObjectId, criteria in('id', [...]) resolves all ids" (happy path via createCriteria)
-        //   - "with storedAs ObjectId, batch getAll with non-hex ids falls back to BSON String in the in-list"
-        //     (null-return fallback for natural keys)
-        queryHandlers.put(In.class, new QueryHandler<In>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, In in, Document query, PersistentEntity entity) {
-                Document inQuery = new Document();
-                List<Object> values = getInListQueryValues(entity, in);
-
-                PersistentProperty identityProp = entity.getIdentity();
-                boolean isIdInList = identityProp != null && identityProp.getName().equals(in.getProperty());
-                if (isIdInList && MongoIdCoercion.resolveStoredAs(entity) != null) {
-                    List<Object> coerced = new ArrayList<>(values.size());
-                    for (Object v : values) {
-                        coerced.add(MongoIdCoercion.coerceIdToStoredType(v, entity));
-                    }
-                    values = coerced;
-                }
-
-                inQuery.put(IN_OPERATOR, values);
-                String propertyName = getPropertyName(entity, in);
-                query.put(propertyName, inQuery);
-            }
-        });
-
-        queryHandlers.put(AssociationQuery.class, new QueryHandler<AssociationQuery>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, AssociationQuery criterion, Document query, PersistentEntity entity) {
-                Association<?> association = criterion.getAssociation();
-                PersistentEntity associatedEntity = association.getAssociatedEntity();
-                if (association instanceof EmbeddedCollection) {
-                    Document associationCollectionQuery = new Document();
-                    populateMongoQuery(queryEncoder, associationCollectionQuery, criterion.getCriteria(), associatedEntity);
-                    Document collectionQuery = new Document("$elemMatch", associationCollectionQuery);
-                    String propertyKey = getPropertyName(entity, association.getName());
-                    query.put(propertyKey, collectionQuery);
-                } else if (associatedEntity instanceof EmbeddedPersistentEntity || association instanceof Embedded) {
-                    Document associatedEntityQuery = new Document();
-                    populateMongoQuery(queryEncoder, associatedEntityQuery, criterion.getCriteria(), associatedEntity);
-                    String propertyKey = getPropertyName(entity, association.getName());
-                    prefixEmbeddedQuery(propertyKey, associatedEntityQuery, query);
-                } else {
-                    throw new UnsupportedOperationException("Join queries are not supported by MongoDB");
-                }
-            }
-        });
-
-        queryHandlers.put(WithinBox.class, new QueryHandler<WithinBox>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, WithinBox withinBox, Document query, PersistentEntity entity) {
-                Document nearQuery = new Document();
-                Document box = new Document();
-                MongoEntityPersister.setDBObjectValue(box, BOX_OPERATOR, withinBox.getValues(), entity.getMappingContext());
-                nearQuery.put(WITHIN_OPERATOR, box);
-                String propertyName = getPropertyName(entity, withinBox);
-                query.put(propertyName, nearQuery);
-            }
-        });
-
-        queryHandlers.put(WithinPolygon.class, new QueryHandler<WithinPolygon>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, WithinPolygon withinPolygon, Document query, PersistentEntity entity) {
-                Document nearQuery = new Document();
-                Document box = new Document();
-                MongoEntityPersister.setDBObjectValue(box, POLYGON_OPERATOR, withinPolygon.getValues(), entity.getMappingContext());
-                nearQuery.put(WITHIN_OPERATOR, box);
-                String propertyName = getPropertyName(entity, withinPolygon);
-                query.put(propertyName, nearQuery);
-            }
-        });
-
-        queryHandlers.put(WithinCircle.class, new QueryHandler<WithinCircle>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, WithinCircle withinCentre, Document query, PersistentEntity entity) {
-                Document nearQuery = new Document();
-                Document center = new Document();
-                MongoEntityPersister.setDBObjectValue(center, CENTER_OPERATOR, withinCentre.getValues(), entity.getMappingContext());
-                nearQuery.put(WITHIN_OPERATOR, center);
-                String propertyName = getPropertyName(entity, withinCentre);
-                query.put(propertyName, nearQuery);
-            }
-        });
-
-        QueryHandler<Near> nearHandler = new QueryHandler<>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, Near near, Document query, PersistentEntity entity) {
-                Document nearQuery = new Document();
-                Object value = near.getValue();
-                String nearOperator = near instanceof NearSphere ? NEAR_SPHERE_OPERATOR : NEAR_OPERATOR;
-                if ((value instanceof List) || (value instanceof Map)) {
-                    MongoEntityPersister.setDBObjectValue(nearQuery, nearOperator, value, entity.getMappingContext());
-                } else if (value instanceof Point) {
-                    Document geoJson = GeoJSONType.convertToGeoDocument((Point) value);
-                    Document geometry = new Document();
-                    geometry.put(GEOMETRY_OPERATOR, geoJson);
-                    if (near.maxDistance != null) {
-                        geometry.put(MAX_DISTANCE_OPERATOR, near.maxDistance.getValue());
-                    }
-                    nearQuery.put(nearOperator, geometry);
-                }
-
-                String propertyName = getPropertyName(entity, near);
-                query.put(propertyName, nearQuery);
-            }
-        };
-        queryHandlers.put(Near.class, nearHandler);
-        queryHandlers.put(NearSphere.class, nearHandler);
-
-        queryHandlers.put(GeoWithin.class, new QueryHandler<GeoWithin>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, GeoWithin geoWithin, Document query, PersistentEntity entity) {
-                Document queryRoot = new Document();
-                Document queryGeoWithin = new Document();
-                queryRoot.put(GEO_WITHIN_OPERATOR, queryGeoWithin);
-                String targetProperty = getPropertyName(entity, geoWithin);
-                Object value = geoWithin.getValue();
-                if (value instanceof Shape) {
-                    Shape shape = (Shape) value;
-                    if (shape instanceof Polygon) {
-                        Polygon p = (Polygon) shape;
-                        Document geoJson = GeoJSONType.convertToGeoDocument(p);
-                        queryGeoWithin.put(GEOMETRY_OPERATOR, geoJson);
-                    } else if (shape instanceof Box) {
-                        queryGeoWithin.put(BOX_OPERATOR, shape.asList());
-                    } else if (shape instanceof Circle) {
-                        queryGeoWithin.put(CENTER_OPERATOR, shape.asList());
-                    } else if (shape instanceof Sphere) {
-                        queryGeoWithin.put(CENTER_SPHERE_OPERATOR, shape.asList());
-                    }
-                } else if (value instanceof Map) {
-                    queryGeoWithin.putAll((Map) value);
-                }
-
-                query.put(targetProperty, queryRoot);
-            }
-        });
-
-        queryHandlers.put(GeoIntersects.class, new QueryHandler<GeoIntersects>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, GeoIntersects geoIntersects, Document query, PersistentEntity entity) {
-                Document queryRoot = new Document();
-                Document queryGeoWithin = new Document();
-                queryRoot.put(GEO_INTERSECTS_OPERATOR, queryGeoWithin);
-                String targetProperty = getPropertyName(entity, geoIntersects);
-                Object value = geoIntersects.getValue();
-                if (value instanceof GeoJSON) {
-                    Shape shape = (Shape) value;
-                    Document geoJson = GeoJSONType.convertToGeoDocument(shape);
-                    queryGeoWithin.put(GEOMETRY_OPERATOR, geoJson);
-                } else if (value instanceof Map) {
-                    queryGeoWithin.putAll((Map) value);
-                }
-
-                query.put(targetProperty, queryRoot);
-            }
-        });
-        queryHandlers.put(Conjunction.class, new QueryHandler<Conjunction>() {
-            public void handle(EmbeddedQueryEncoder queryEncoder, Conjunction criterion, Document query, PersistentEntity entity) {
-                populateMongoQuery(queryEncoder, query, criterion, entity);
-            }
-        });
-
-        queryHandlers.put(Disjunction.class, new QueryHandler<Disjunction>() {
-            @SuppressWarnings("unchecked")
-            public void handle(EmbeddedQueryEncoder queryEncoder, Disjunction criterion, Document query, PersistentEntity entity) {
-                populateMongoQuery(queryEncoder, query, criterion, entity);
-            }
-        });
-
-        groupByProjectionHandlers.put(AvgProjection.class, new ProjectionHandler<AvgProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, AvgProjection projection) {
-                return addProjectionToGroupBy(projectObject, groupBy, projection, AVERAGE_OPERATOR, "avg_");
-            }
-        });
-        groupByProjectionHandlers.put(CountProjection.class, new ProjectionHandler<CountProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, CountProjection projection) {
-                projectObject.put(MongoEntityPersister.MONGO_ID_FIELD, 1);
-                String projectionKey = "count";
-                groupBy.put(projectionKey, new Document(SUM_OPERATOR, 1));
-                return projectionKey;
-            }
-        });
-        groupByProjectionHandlers.put(CountDistinctProjection.class, new ProjectionHandler<CountDistinctProjection>() {
-            @Override
-            // equivalent of "select count (distinct fieldName) from someTable". Example:
-            // db.someCollection.aggregate([{ $group: { _id: "$fieldName"}  },{ $group: { _id: 1, count: { $sum: 1 } } } ])
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, CountDistinctProjection projection) {
-                projectObject.put(projection.getPropertyName(), 1);
-                String property = projection.getPropertyName();
-                String projectionValueKey = "countDistinct_" + property;
-                Document id = getIdObjectForGroupBy(groupBy);
-                id.put(projectionValueKey, "$" + property);
-                return projectionValueKey;
-            }
-        });
-
-        groupByProjectionHandlers.put(MinProjection.class, new ProjectionHandler<MinProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, MinProjection projection) {
-                return addProjectionToGroupBy(projectObject, groupBy, projection, MIN_OPERATOR, "min_");
-            }
-        });
-        groupByProjectionHandlers.put(MaxProjection.class, new ProjectionHandler<MaxProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, MaxProjection projection) {
-                return addProjectionToGroupBy(projectObject, groupBy, projection, MAX_OPERATOR, "max_");
-            }
-        });
-        groupByProjectionHandlers.put(SumProjection.class, new ProjectionHandler<SumProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, SumProjection projection) {
-                return addProjectionToGroupBy(projectObject, groupBy, projection, SUM_OPERATOR, "sum_");
-            }
-        });
-
-        projectProjectionHandlers.put(DistinctPropertyProjection.class, new ProjectionHandler<DistinctPropertyProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, DistinctPropertyProjection projection) {
-                String property = projection.getPropertyName();
-                projectObject.put(property, 1);
-                Document id = getIdObjectForGroupBy(groupBy);
-                String projectedValueKey = property.replace('.', '_');
-                id.put(projectedValueKey, "$" + property);
-                return projectedValueKey;
-            }
-        });
-
-        projectProjectionHandlers.put(PropertyProjection.class, new ProjectionHandler<PropertyProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, PropertyProjection projection) {
-                String property = projection.getPropertyName();
-                projectObject.put(property, 1);
-                Document id = getIdObjectForGroupBy(groupBy);
-                String projectedValueKey = property.replace('.', '_');
-                id.put(projectedValueKey, "$" + property);
-                // we add the id to the grouping to make it not distinct
-                id.put(MongoEntityPersister.MONGO_ID_FIELD, "$" + MongoEntityPersister.MONGO_ID_FIELD);
-                return projectedValueKey;
-            }
-        });
-
-        projectProjectionHandlers.put(IdProjection.class, new ProjectionHandler<IdProjection>() {
-            @Override
-            public String handle(PersistentEntity entity, Document projectObject, Document groupBy, IdProjection projection) {
-                projectObject.put(MongoEntityPersister.MONGO_ID_FIELD, 1);
-                Document id = getIdObjectForGroupBy(groupBy);
-                id.put(MongoEntityPersister.MONGO_ID_FIELD, "$_id");
-
-                return MongoEntityPersister.MONGO_ID_FIELD;
-            }
-        });
-
-    }
-
-    private static Document getIdObjectForGroupBy(Document groupBy) {
-        Object value = groupBy.get(MongoEntityPersister.MONGO_ID_FIELD);
-        Document id;
-        if (value instanceof Document) {
-            id = (Document) value;
-        } else {
-            id = new Document();
-            groupBy.put(MongoEntityPersister.MONGO_ID_FIELD, id);
-        }
-        return id;
-    }
-
-    private static String addProjectionToGroupBy(Document projectObject, Document groupBy, PropertyProjection projection, String operator, String prefix) {
-        projectObject.put(projection.getPropertyName(), 1);
-        String property = projection.getPropertyName();
-        String projectionValueKey = prefix + property.replace('.', '_');
-        Document averageProjection = new Document(operator, "$" + property);
-        groupBy.put(projectionValueKey, averageProjection);
-        return projectionValueKey;
-    }
-
-    private final AbstractMongoSession mongoSession;
-    private final EntityPersister mongoEntityPersister;
-    private final ManualProjections manualProjections;
-    private boolean isCodecPersister = false;
-
-    public MongoQuery(AbstractMongoSession session, PersistentEntity entity) {
-        super(session, entity);
-        this.mongoSession = session;
-        this.manualProjections = new ManualProjections(entity);
-        if (session != null) {
-
-            this.mongoEntityPersister = (EntityPersister) session.getPersister(entity);
-            if (this.mongoEntityPersister instanceof MongoCodecEntityPersister) {
-                this.isCodecPersister = true;
-            }
-        }
-        else {
-            mongoEntityPersister = null;
-        }
-    }
-
-    @Override
-    protected void flushBeforeQuery() {
-        // with Mongo we only flush the session if a transaction is not active to allow for session-managed transactions
-        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
-            super.flushBeforeQuery();
-        }
-    }
-
-    /**
-     * Gets the Mongo query for this query instance
-     *
-     * @return The Mongo query
-     */
-    public Document getMongoQuery() {
-        Document query = createQueryObject(entity);
-        populateMongoQuery((AbstractMongoSession) getSession(), query, criteria, entity);
-        return query;
-    }
-
-    @Override
-    protected List executeQuery(final PersistentEntity entity, final Junction criteria) {
-        final AbstractMongoSession mongoSession = this.mongoSession;
-        com.mongodb.client.MongoCollection<Document> collection = mongoSession.getCollection(entity);
-
-        final List<Projection> projectionList = projections().getProjectionList();
-        boolean hasOnlyDistinct = projectionList.size() == 1 && (projectionList.get(0) instanceof DistinctProjection);
-        if (uniqueResult && (projectionList.isEmpty() || hasOnlyDistinct)) {
-            if (isCodecPersister) {
-                collection = (com.mongodb.client.MongoCollection<Document>) (com.mongodb.client.MongoCollection) collection
-                        .withDocumentClass(entity.getJavaClass());
-            }
-            final Object dbObject;
-            if (criteria.isEmpty()) {
-                FindIterable<Document> cursor = mongoSession.find(collection, createQueryObject(entity));
-
-                dbObject = ((FindIterable<Document>) setHint(cursor)).limit(1)
-                        .first();
-            } else {
-                FindIterable<Document> cursor = mongoSession.find(collection, getMongoQuery());
-
-                dbObject = ((FindIterable<Document>) setHint(cursor)).limit(1)
-                        .first();
-            }
-            if (dbObject == null) {
-                return wrapObjectResultInList(dbObject);
-            }
-            if (isCodecPersister) {
-                if (!mongoSession.contains(dbObject)) {
-                    final EntityAccess entityAccess = mongoSession.createEntityAccess(entity, dbObject);
-                    mongoEntityPersister.firePostLoadEvent(entity, entityAccess);
-                    mongoSession.cacheInstance(dbObject.getClass(), (Serializable) entityAccess.getIdentifier(), dbObject);
-                }
-                return wrapObjectResultInList(dbObject);
-            }
-            else {
-                return wrapObjectResultInList(createObjectFromDBObject((Document) dbObject));
-            }
-        }
-
-        MongoCursor<Document> cursor;
-        Document query = createQueryObject(entity);
-
-        if (projectionList.isEmpty() || hasOnlyDistinct) {
-            if (isCodecPersister) {
-                collection = (com.mongodb.client.MongoCollection<Document>) (com.mongodb.client.MongoCollection) collection
-                        .withDocumentClass(entity.getJavaClass())
-                        .withCodecRegistry(mongoSession.getDatastore().getCodecRegistry());
-            }
-            cursor = executeQuery(entity, criteria, collection, query);
-            return new MongoResultList(cursor, offset, mongoEntityPersister);
-        }
-
-        populateMongoQuery((AbstractMongoSession) session, query, criteria, entity);
-        AggregatePipeline aggregatePipeline = buildAggregatePipeline(entity, query, projectionList);
-        List<Document> aggregationPipeline = aggregatePipeline.getAggregationPipeline();
-        boolean singleResult = aggregatePipeline.isSingleResult();
-        List<ProjectedProperty> projectedKeys = aggregatePipeline.getProjectedKeys();
-        List projectedResults = new ArrayList();
-
-        AggregateIterable<Document> aggregatedResults = mongoSession.aggregate(collection, aggregationPipeline);
-        aggregatedResults = (AggregateIterable<Document>) setHint(aggregatedResults);
-        final MongoCursor<Document> aggregateCursor = aggregatedResults.iterator();
-
-        if (singleResult && aggregateCursor.hasNext()) {
-            Document dbo = aggregateCursor.next();
-            for (ProjectedProperty projectedProperty : projectedKeys) {
-                Object value = dbo.get(projectedProperty.projectionKey);
-                PersistentProperty property = projectedProperty.property;
-                if (value != null) {
-                    if (property instanceof ToOne) {
-                        projectedResults.add(session.retrieve(property.getType(), (Serializable) value));
-                    } else {
-                        projectedResults.add(value);
-                    }
-                } else {
-                    if (projectedProperty.projection instanceof CountProjection) {
-                        projectedResults.add(0);
-                    }
-                }
-            }
-        } else {
-            return new AggregatedResultList((AbstractMongoSession) getSession(), aggregateCursor, projectedKeys);
-        }
-
-        return projectedResults;
-
-    }
-
-    protected AggregatePipeline buildAggregatePipeline(PersistentEntity entity, Document query, List<Projection> projectionList) {
-        return new AggregatePipeline(this, entity, query, projectionList).build();
-    }
-
-    protected MongoCursor<Document> executeQuery(final PersistentEntity entity,
-                                                 final Junction criteria,
-                                                 final com.mongodb.client.MongoCollection<Document> collection,
-                                                 final Document query) {
-        FindIterable<Document> cursor;
-        if (criteria.isEmpty()) {
-            cursor = executeQueryAndApplyPagination(collection, query);
-        } else {
-            populateMongoQuery((AbstractMongoSession) session, query, criteria, entity);
-            cursor = executeQueryAndApplyPagination(collection, query);
-        }
-
-        cursor = (FindIterable<Document>) setHint(cursor);
-
-        return cursor.iterator();
-    }
-
-    private MongoIterable<Document> setHint(MongoIterable<Document> cursor) {
-        MongoIterable<Document> result = cursor;
-
-        if (queryArguments != null) {
-            if (queryArguments.containsKey(HINT_ARGUMENT)) {
-                Object hint = queryArguments.get(HINT_ARGUMENT);
-                if (hint instanceof Map) {
-                    if (cursor instanceof FindIterable) {
-                        result = ((FindIterable) cursor).hint(new Document((Map<String, Object>) hint));
-                    } else if (cursor instanceof AggregateIterable) {
-                        result = ((AggregateIterable) cursor).hint(new Document((Map<String, Object>) hint));
-                    }
-                } else {
-                    if (cursor instanceof FindIterable) {
-                        result = ((FindIterable) cursor).hintString(hint.toString());
-                    } else if (cursor instanceof AggregateIterable) {
-                        result = ((AggregateIterable) cursor).hintString(hint.toString());
-                    }
-                }
-            }
-        }
-
-        return result;
-    }
-
-    protected FindIterable<Document> executeQueryAndApplyPagination(com.mongodb.client.MongoCollection<Document> collection, Document query) {
-        Object readConcernObject = queryArguments.get(READ_CONCERN_ARGUMENT);
-        if (readConcernObject instanceof ReadConcern) {
-            collection = collection.withReadConcern(
-                (ReadConcern) readConcernObject
-            );
-        }
-
-        final FindIterable<Document> iterable = mongoSession.find(collection, query);
-        if (offset != null && offset > 0) {
-            iterable.skip(offset);
-        }
-        if (max != null && max > -1) {
-            iterable.limit(max);
-        }
-        if (uniqueResult) {
-            iterable.limit(1);
-        }
-
-        if (!orderBy.isEmpty()) {
-            Document orderObject = new Document();
-            for (Order order : orderBy) {
-                String property = order.getProperty();
-                property = getPropertyName(entity, property);
-                orderObject.put(property, order.getDirection() == Order.Direction.DESC ? -1 : 1);
-            }
-            iterable.sort(orderObject);
-        } else {
-            MongoCollection coll = (MongoCollection) entity.getMapping().getMappedForm();
-            if (coll != null && coll.getSort() != null) {
-                Document orderObject = new Document();
-                Order order = coll.getSort();
-                String property = order.getProperty();
-                property = getPropertyName(entity, property);
-                orderObject.put(property, order.getDirection() == Order.Direction.DESC ? -1 : 1);
-                iterable.sort(orderObject);
-            }
-        }
-
-        return iterable;
-    }
-
-    private Document getClassFieldDocument(final PersistentEntity entity) {
-        Object classFieldValue;
-        Collection<PersistentEntity> childEntities = entity.getMappingContext().getChildEntities(entity);
-        if (childEntities.size() > 0) {
-            HashMap classValue = new HashMap<>();
-            ArrayList classes = new ArrayList<>();
-            classes.add(entity.getDiscriminator());
-            for (PersistentEntity childEntity: childEntities) {
-                classes.add(childEntity.getDiscriminator());
-            }
-            classValue.put(MONGO_IN_OPERATOR, classes);
-            classFieldValue = classValue;
-        } else {
-            classFieldValue = entity.getDiscriminator();
-        }
-        return new Document(MongoEntityPersister.MONGO_CLASS_FIELD, classFieldValue);
-    }
-
-    protected Document createQueryObject(PersistentEntity persistentEntity) {
-        Document query;
-        if (persistentEntity.isRoot()) {
-            query = new Document();
-        } else {
-            query = getClassFieldDocument(persistentEntity);
-        }
-        return query;
-    }
-
-    public static void populateMongoQuery(final AbstractMongoSession session, Document query, Junction criteria, final PersistentEntity entity) {
-        EmbeddedQueryEncoder queryEncoder;
-        if (session instanceof MongoCodecSession) {
-            final MongoDatastore datastore = session.getDatastore();
-            final CodecRegistry codecRegistry = datastore.getCodecRegistry();
-            queryEncoder = new EmbeddedQueryEncoder() {
-                @Override
-                public Object encode(Embedded embedded, Object instance) {
-                    final PersistentEntityCodec codec = (PersistentEntityCodec) codecRegistry.get(embedded.getType());
-                    final BsonDocument doc = new BsonDocument();
-                    codec.encode(new BsonDocumentWriter(doc), instance, ENCODER_CONTEXT, false);
-                    return doc;
-                }
-            };
-        }
-        else {
-            queryEncoder = new EmbeddedQueryEncoder() {
-                @Override
-                public Object encode(Embedded embedded, Object instance) {
-                    MongoEntityPersister persister = (MongoEntityPersister) session.getPersister(entity.getJavaClass());
-                    return persister.createNativeObjectForEmbedded(embedded, instance);
-
-                }
-            };
-        }
-
-        populateMongoQuery(queryEncoder, query, criteria, entity);
-    }
-
-    @SuppressWarnings("unchecked")
-    public static void populateMongoQuery(final EmbeddedQueryEncoder queryEncoder, Document query, Junction criteria, final PersistentEntity entity) {
-        List subList = null;
-        // if a query combines more than 1 item, wrap the items in individual $and or $or arguments
-        // so that property names can't clash (e.g. for an $and containing two $ors)
-        if (criteria.getCriteria().size() > 1) {
-            if (criteria instanceof Disjunction) {
-                subList = new ArrayList();
-                query.put(OR_OPERATOR, subList);
-            } else if (criteria instanceof Conjunction) {
-                subList = new ArrayList();
-                query.put(AND_OPERATOR, subList);
-            }
-        }
-        for (Criterion criterion : criteria.getCriteria()) {
-            final QueryHandler queryHandler = queryHandlers.get(criterion.getClass());
-            if (queryHandler != null) {
-                Document dbo = query;
-                if (subList != null) {
-                    dbo = new Document();
-                    subList.add(dbo);
-                }
-
-                if (criterion instanceof PropertyCriterion && !(criterion instanceof GeoCriterion)) {
-                    PropertyCriterion pc = (PropertyCriterion) criterion;
-                    PersistentProperty property = entity.getPropertyByName(pc.getProperty());
-                    if (property instanceof Custom) {
-                        CustomTypeMarshaller customTypeMarshaller = ((Custom) property).getCustomTypeMarshaller();
-                        if (!(customTypeMarshaller instanceof CodecCustomTypeMarshaller)) {
-                            customTypeMarshaller.query(property, pc, query);
-                            continue;
-                        }
-                    }
-                }
-                queryHandler.handle(queryEncoder, criterion, dbo, entity);
-            } else {
-                throw new InvalidDataAccessResourceUsageException("Queries of type " + criterion.getClass().getSimpleName() + " are not supported by this implementation");
-            }
-        }
-    }
-
-    /**
-     * Rewrites a query built against an embedded entity so it applies to the owning document,
-     * qualifying each property name with the embedded property's path. Logical operators such as
-     * {@code $and} and {@code $or} must stay at the current level (a key like {@code extRef1.$and}
-     * matches nothing), so their nested documents are rewritten recursively instead.
-     */
-    private static void prefixEmbeddedQuery(String prefix, Document source, Document target) {
-        for (String key : source.keySet()) {
-            Object value = source.get(key);
-            if (key.charAt(0) == '$') {
-                if (!(value instanceof List)) {
-                    // A top-level operator whose value is not a rewritable list of clauses
-                    // (e.g. $where from a property-to-property comparison, or $text) cannot be
-                    // qualified with the embedded path - prefixing it would silently match nothing.
-                    throw new UnsupportedOperationException("Criterion [" + key +
-                            "] is not supported inside an embedded association query");
-                }
-                List<Object> rewritten = new ArrayList<>();
-                for (Object element : (List<?>) value) {
-                    if (element instanceof Document) {
-                        Document rewrittenElement = new Document();
-                        prefixEmbeddedQuery(prefix, (Document) element, rewrittenElement);
-                        rewritten.add(rewrittenElement);
-                    } else {
-                        rewritten.add(element);
-                    }
-                }
-                target.put(key, rewritten);
-            } else {
-                target.put(prefix + '.' + key, value);
-            }
-        }
-    }
-
-    private Object createObjectFromDBObject(Document dbObject) {
-        // we always use the session cached version where available.
-        final Object id = dbObject.get(MongoEntityPersister.MONGO_ID_FIELD);
-        Class type = mongoEntityPersister.getPersistentEntity().getJavaClass();
-        Object instance = mongoSession.getCachedInstance(type, (Serializable) id);
-        if (instance == null) {
-            instance = ((MongoEntityPersister) mongoEntityPersister).createObjectFromNativeEntry(
-                    mongoEntityPersister.getPersistentEntity(), (Serializable) id, dbObject);
-            mongoSession.cacheInstance(type, (Serializable) id, instance);
-        }
-        // note cached instances may be stale, but user can call 'refresh' to fix that.
-        return instance;
-    }
-
-    @SuppressWarnings("unchecked")
-    private List wrapObjectResultInList(Object object) {
-        List result = new ArrayList();
-        result.add(object);
-        return result;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query near(String property, List value) {
-        add(new Near(property, value));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query near(String property, Point value) {
-        add(new Near(property, value));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query near(String property, List value, Distance maxDistance) {
-        add(new Near(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query near(String property, Point value, Distance maxDistance) {
-        add(new Near(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query near(String property, List value, Number maxDistance) {
-        add(new Near(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query near(String property, Point value, Number maxDistance) {
-        add(new Near(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query nearSphere(String property, List value) {
-        add(new NearSphere(property, value));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query nearSphere(String property, Point value) {
-        add(new NearSphere(property, value));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query nearSphere(String property, List value, Distance maxDistance) {
-        add(new NearSphere(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query nearSphere(String property, Point value, Distance maxDistance) {
-        add(new NearSphere(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query nearSphere(String property, List value, Number maxDistance) {
-        add(new NearSphere(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values near the given two dimensional list
-     *
-     * @param property The property
-     * @param value    A two dimensional list of values
-     * @return this
-     */
-    public Query nearSphere(String property, Point value, Number maxDistance) {
-        add(new NearSphere(property, value, maxDistance));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values within a given box. A box is defined as a multi-dimensional list in the form
-     * [[40.73083, -73.99756], [40.741404,  -73.988135]]
-     *
-     * @param property The property
-     * @param value    A multi-dimensional list of values
-     * @return This query
-     */
-    public Query withinBox(String property, List value) {
-        add(new WithinBox(property, value));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values within the given shape
-     *
-     * @param property The property
-     * @param shape    The shape
-     * @return The query instance
-     */
-    public Query geoWithin(String property, Shape shape) {
-        add(new GeoWithin(property, shape));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values within the given shape
-     *
-     * @param property The property
-     * @param shape    The shape
-     * @return The query instance
-     */
-    public Query geoIntersects(String property, GeoJSON shape) {
-        add(new GeoIntersects(property, shape));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values within a given polygon. A polygon is defined as a multi-dimensional list in the form
-     * [[0, 0], [3, 6], [6, 0]]
-     *
-     * @param property The property
-     * @param value    A multi-dimensional list of values
-     * @return This query
-     */
-    public Query withinPolygon(String property, List value) {
-        add(new WithinPolygon(property, value));
-        return this;
-    }
-
-    /**
-     * Geospacial query for values within a given circle. A circle is defined as a multi-dimensial list containing the position of the center and the radius:
-     * [[50, 50], 10]
-     *
-     * @param property The property
-     * @param value    A multi-dimensional list of values
-     * @return This query
-     */
-    public Query withinCircle(String property, List value) {
-        add(new WithinBox(property, value));
-        return this;
-    }
-
-    /**
-     * @param arguments The query arguments
-     */
-    public void setArguments(Map arguments) {
-        this.queryArguments = arguments;
-    }
-
-    /**
-     * Used for Geospacial querying
-     *
-     * @author Graeme Rocher
-     * @since 1.0
-     */
-    public static class Near extends GeoCriterion {
-
-        Distance maxDistance = null;
-
-        public Near(String name, Object value) {
-            super(name, value);
-        }
-
-        public Near(String name, Object value, Distance maxDistance) {
-            super(name, value);
-            this.maxDistance = maxDistance;
-        }
-
-        public Near(String name, Object value, Number maxDistance) {
-            super(name, value);
-            this.maxDistance = Distance.valueOf(maxDistance.doubleValue());
-        }
-
-        public void setMaxDistance(Distance maxDistance) {
-            this.maxDistance = maxDistance;
-        }
-    }
-
-    /**
-     * Used for Geospacial querying with the $nearSphere operator
-     *
-     * @author Graeme Rocher
-     * @since 1.0
-     */
-    public static class NearSphere extends Near {
-        public NearSphere(String name, Object value) {
-            super(name, value);
-        }
-
-        public NearSphere(String name, Object value, Distance maxDistance) {
-            super(name, value, maxDistance);
-        }
-
-        public NearSphere(String name, Object value, Number maxDistance) {
-            super(name, value, maxDistance);
-        }
-    }
-
-    /**
-     * Used for Geospacial querying of boxes
-     *
-     * @author Graeme Rocher
-     * @since 1.0
-     */
-    public static class WithinBox extends PropertyCriterion {
-
-        public WithinBox(String name, List value) {
-            super(name, value);
-        }
-
-        public List getValues() {
-            return (List) getValue();
-        }
-
-        public void setValue(List matrix) {
-            this.value = matrix;
-        }
-    }
-
-    /**
-     * Used for Geospacial querying of polygons
-     */
-    public static class WithinPolygon extends PropertyCriterion {
-
-        public WithinPolygon(String name, List value) {
-            super(name, value);
-        }
-
-        public List getValues() {
-            return (List) getValue();
-        }
-
-        public void setValue(List value) {
-            this.value = value;
-        }
-    }
-
-    /**
-     * Used for Geospacial querying of circles
-     *
-     * @author Graeme Rocher
-     * @since 1.0
-     */
-    public static class WithinCircle extends PropertyCriterion {
-
-        public WithinCircle(String name, List value) {
-            super(name, value);
-        }
-
-        public List getValues() {
-            return (List) getValue();
-        }
-
-        public void setValue(List matrix) {
-            this.value = matrix;
-        }
-    }
-
-    /**
-     * Used for all GeoSpacial queries using 2dsphere indexes
-     */
-    public static class GeoCriterion extends PropertyCriterion {
-
-        public GeoCriterion(String name, Object value) {
-            super(name, value);
-        }
-    }
-
-    public static class GeoWithin extends GeoCriterion {
-        public GeoWithin(String name, Object value) {
-            super(name, value);
-        }
-    }
-
-    public static class GeoIntersects extends GeoCriterion {
-        public GeoIntersects(String name, Object value) {
-            super(name, value);
-        }
-    }
-
-    public static class AggregatedResultList extends AbstractList implements Closeable {
-
-        private MongoCursor cursor;
-        private List<ProjectedProperty> projectedProperties;
-        private List initializedObjects = new ArrayList();
-        private int internalIndex = 0;
-        private boolean initialized = false;
-        private boolean containsAssociations = false;
-        private AbstractMongoSession session;
-
-        public AggregatedResultList(AbstractMongoSession session, MongoCursor<Document> cursor, List<ProjectedProperty> projectedProperties) {
-            this.cursor = cursor;
-            this.projectedProperties = projectedProperties;
-            this.session = session;
-            for (ProjectedProperty projectedProperty : projectedProperties) {
-                if (projectedProperty.property instanceof Association) {
-                    this.containsAssociations = true;
-                    break;
-                }
-            }
-        }
-
-        @Override
-        public String toString() {
-            return initializedObjects.toString();
-        }
-
-        @Override
-        public Object get(int index) {
-            if (containsAssociations) initializeFully();
-            if (initializedObjects.size() > index) {
-                return initializedObjects.get(index);
-            } else if (!initialized) {
-                boolean hasResults = false;
-                while (cursor.hasNext()) {
-                    hasResults = true;
-                    Document dbo = (Document) cursor.next();
-                    Object projected = addInitializedObject(dbo);
-                    if (index == internalIndex) {
-                        return projected;
-                    }
-                }
-                if (!hasResults) handleNoResults();
-                initialized = true;
-            }
-            throw new ArrayIndexOutOfBoundsException("Index value " + index + " exceeds size of aggregate list");
-        }
-
-        @Override
-        public Object set(int index, Object element) {
-            initializeFully();
-            return initializedObjects.set(index, element);
-        }
-
-        @Override
-        public ListIterator listIterator() {
-            return listIterator(0);
-        }
-
-        @Override
-        public ListIterator listIterator(int index) {
-            initializeFully();
-            return initializedObjects.listIterator(index);
-        }
-
-        protected void initializeFully() {
-            if (initialized) return;
-            if (containsAssociations) {
-                if (projectedProperties.size() == 1) {
-                    ProjectedProperty projectedProperty = projectedProperties.get(0);
-                    PersistentProperty property = projectedProperty.property;
-                    List<Serializable> identifiers = new ArrayList<>();
-                    boolean hasResults = false;
-                    while (cursor.hasNext()) {
-                        hasResults = true;
-                        Document dbo = (Document) cursor.next();
-                        Object id = getProjectedValue(dbo, projectedProperty.projectionKey);
-                        identifiers.add((Serializable) id);
-                    }
-                    if (!hasResults) {
-                        handleNoResults();
-                    }
-                    else if (property instanceof Embedded) {
-                        Embedded embedded = (Embedded) property;
-                        List embeddedList = new ArrayList();
-                        CodecRegistry codecRegistry = session.getDatastore().getCodecRegistry();
-                        PersistentEntityCodec codec = new PersistentEntityCodec(codecRegistry, embedded.getAssociatedEntity());
-
-                        for (Serializable embeddedDoc : identifiers) {
-                            if (embeddedDoc instanceof Document) {
-                                Document documentObject = (Document) embeddedDoc;
-
-                                Object decoded = codec.decode(new BsonDocumentReader(documentObject.toBsonDocument(Document.class, codecRegistry)));
-                                embeddedList.add(
-                                    decoded
-                                );
-                            }
-                        }
-
-                        this.initializedObjects = embeddedList;
-                    }
-                    else {
-                        this.initializedObjects = session.retrieveAll(property.getType(), identifiers);
-                    }
-                } else {
-                    Map<Integer, Map<Class, List<Serializable>>> associationMap = createAssociationMap();
-
-                    boolean hasResults = false;
-                    while (cursor.hasNext()) {
-                        hasResults = true;
-                        Document dbo = (Document) cursor.next();
-                        List<Object> projectedResult = new ArrayList<>();
-                        int index = 0;
-                        for (ProjectedProperty projectedProperty : projectedProperties) {
-                            PersistentProperty property = projectedProperty.property;
-                            Object value = getProjectedValue(dbo, projectedProperty.projectionKey);
-                            if (property instanceof Association) {
-                                if ((!(property instanceof Embedded) && !(property instanceof EmbeddedCollection) && !(property instanceof Basic))) {
-                                    Map<Class, List<Serializable>> identifierMap = associationMap.get(index);
-                                    Class type = ((Association) property).getAssociatedEntity().getJavaClass();
-                                    identifierMap.get(type).add((Serializable) value);
-                                }
-                            }
-                            projectedResult.add(value);
-                            index++;
-                        }
-
-                        initializedObjects.add(projectedResult);
-                    }
-
-                    if (!hasResults) {
-                        handleNoResults();
-                        return;
-                    }
-
-                    Map<Integer, List> finalResults = new HashMap<>();
-                    for (Integer index : associationMap.keySet()) {
-                        Map<Class, List<Serializable>> associatedEntityIdentifiers = associationMap.get(index);
-                        for (Class associationClass : associatedEntityIdentifiers.keySet()) {
-                            List<Serializable> identifiers = associatedEntityIdentifiers.get(associationClass);
-                            finalResults.put(index, session.retrieveAll(associationClass, identifiers));
-                        }
-                    }
-
-                    for (Object initializedObject : initializedObjects) {
-                        List projected = (List) initializedObject;
-                        for (Integer index : finalResults.keySet()) {
-                            List resultsByIndex = finalResults.get(index);
-                            if (index < resultsByIndex.size()) {
-                                projected.set(index, resultsByIndex.get(index));
-                            } else {
-                                projected.set(index, null);
-                            }
-                        }
-
-                    }
-                }
-            } else {
-                boolean hasResults = false;
-                while (cursor.hasNext()) {
-                    hasResults = true;
-                    Document dbo = (Document) cursor.next();
-                    addInitializedObject(dbo);
-                }
-                if (!hasResults) {
-                    handleNoResults();
-                }
-            }
-            initialized = true;
-        }
-
-        protected void handleNoResults() {
-            ProjectedProperty projectedProperty = projectedProperties.get(0);
-            if (projectedProperty.projection instanceof CountProjection) {
-                initializedObjects.add(0);
-            }
-        }
-
-        private Map<Integer, Map<Class, List<Serializable>>> createAssociationMap() {
-            Map<Integer, Map<Class, List<Serializable>>> associationMap = new HashMap<>();
-            associationMap = DefaultGroovyMethods.withDefault(associationMap, new Closure(this) {
-                public Object doCall(Object o) {
-                    Map<Class, List<Serializable>> subMap = new HashMap<>();
-                    subMap = DefaultGroovyMethods.withDefault(subMap, new Closure(this) {
-                        public Object doCall(Object o) {
-                            return new ArrayList<Serializable>();
-                        }
-                    });
-                    return subMap;
-                }
-            });
-            return associationMap;
-        }
-
-        @Override
-        public Iterator iterator() {
-            if (initialized || containsAssociations || internalIndex > 0) {
-                initializeFully();
-                return initializedObjects.iterator();
-            }
-
-            if (!cursor.hasNext()) {
-                handleNoResults();
-                return initializedObjects.iterator();
-            }
-
-            return new Iterator() {
-                @Override
-                public boolean hasNext() {
-                    boolean hasMore = cursor.hasNext();
-                    if (!hasMore) initialized = true;
-                    return hasMore;
-                }
-
-                @Override
-                public Object next() {
-                    Document dbo = (Document) cursor.next();
-                    return addInitializedObject(dbo);
-                }
-
-                @Override
-                public void remove() {
-                    throw new UnsupportedOperationException("Aggregate result list cannot be mutated.");
-                }
-            };
-        }
-
-        private Object addInitializedObject(Document dbo) {
-            if (projectedProperties.size() > 1) {
-
-                List<Object> projected = new ArrayList<>();
-                for (ProjectedProperty projectedProperty : projectedProperties) {
-                    Object value;
-                    value = getProjectedValue(dbo, projectedProperty.projectionKey);
-                    projected.add(value);
-                }
-                initializedObjects.add(internalIndex, projected);
-                internalIndex++;
-                return projected;
-            } else {
-                ProjectedProperty projectedProperty = projectedProperties.get(0);
-                Object projected = getProjectedValue(dbo, projectedProperty.projectionKey);
-                initializedObjects.add(internalIndex, projected);
-                internalIndex++;
-                return projected;
-
-            }
-        }
-
-        private Object getProjectedValue(Document dbo, String projectionKey) {
-            Object value;
-            if (projectionKey.startsWith("id.")) {
-                projectionKey = projectionKey.substring(3);
-                Document id = (Document) dbo.get(MongoEntityPersister.MONGO_ID_FIELD);
-                value = id.get(projectionKey);
-            } else {
-                value = dbo.get(projectionKey);
-            }
-            return value;
-        }
-
-        @Override
-        public int size() {
-            initializeFully();
-            return initializedObjects.size();
-        }
-
-        @Override
-        public void close() throws IOException {
-            cursor.close();
-        }
-    }
-
-    @SuppressWarnings("serial")
-    public static class MongoResultList extends AbstractResultList {
-
-        private EntityPersister mongoEntityPersister;
-        private MongoCursor cursor;
-        private boolean isCodecPersister;
-
-        @SuppressWarnings("unchecked")
-        public MongoResultList(MongoCursor cursor, Integer offset, EntityPersister mongoEntityPersister) {
-            super(offset == null ? 0 : offset, cursor);
-            this.cursor = cursor;
-            this.mongoEntityPersister = mongoEntityPersister;
-            this.isCodecPersister = mongoEntityPersister instanceof MongoCodecEntityPersister;
-        }
-
-        @Override
-        public void close() throws IOException {
-            cursor.close();
-        }
-
-        @Override
-        public String toString() {
-            initializeFully();
-            return initializedObjects.toString();
-        }
-
-        /**
-         * @return The underlying MongoDB cursor instance
-         */
-        public MongoCursor getCursor() {
-            return cursor;
-        }
-
-        @Override
-        protected Object nextDecoded() {
-            final Object o = cursor.next();
-            if (isCodecPersister) {
-                final AbstractMongoSession session = (AbstractMongoSession) mongoEntityPersister.getSession();
-                if (!session.contains(o)) {
-                    final PersistentEntity entity = mongoEntityPersister.getPersistentEntity();
-                    final EntityAccess entityAccess = session.createEntityAccess(entity, o);
-                    final Object id = entityAccess.getIdentifier();
-                    if (id != null) {
-                        session.cacheInstance(entity.getJavaClass(), (Serializable) id, o);
-                    }
-                    mongoEntityPersister.firePostLoadEvent(entity, entityAccess);
-                }
-            }
-            return o;
-        }
-
-        @Override
-        protected Object convertObject(Object object) {
-            return isCodecPersister ? object : convertDBObject(object);
-        }
-
-        protected Object convertDBObject(Object object) {
-            if (mongoEntityPersister instanceof MongoCodecEntityPersister) {
-                return object;
-            } else {
-                final Document dbObject = (Document) object;
-                Object id = dbObject.get(MongoEntityPersister.MONGO_ID_FIELD);
-                SessionImplementor session = (SessionImplementor) mongoEntityPersister.getSession();
-                Class type = mongoEntityPersister.getPersistentEntity().getJavaClass();
-                Object instance = session.getCachedInstance(type, (Serializable) id);
-                if (instance == null) {
-                    final MongoEntityPersister mep = (MongoEntityPersister) this.mongoEntityPersister;
-                    instance = mep.createObjectFromNativeEntry(
-                            this.mongoEntityPersister.getPersistentEntity(), (Serializable) id, dbObject);
-                    session.cacheInstance(type, (Serializable) id, instance);
-                }
-                return instance;
-            }
-        }
-
-    }
-
-    public static class ProjectedProperty {
-        public Projection projection;
-        public String projectionKey;
-        public PersistentProperty property;
-    }
-
-    protected static class AggregatePipeline {
-        private PersistentEntity entity;
-        private Document query;
-        private List<Projection> projectionList;
-        private List<Document> aggregationPipeline;
-        private List<ProjectedProperty> projectedKeys;
-        private boolean singleResult;
-        private final MongoQuery mongoQuery;
-
-        public AggregatePipeline(MongoQuery mongoQuery, PersistentEntity entity, Document queryObject, List<Projection> projectionList) {
-            this.mongoQuery = mongoQuery;
-            this.entity = entity;
-            this.query = queryObject;
-            this.projectionList = projectionList;
-        }
-
-        public List<Document> getAggregationPipeline() {
-            return aggregationPipeline;
-        }
-
-        public List<ProjectedProperty> getProjectedKeys() {
-            return projectedKeys;
-        }
-
-        public boolean isSingleResult() {
-            return singleResult;
-        }
-
-        public AggregatePipeline build() {
-            aggregationPipeline = new ArrayList<>();
-
-            if (!query.keySet().isEmpty()) {
-                aggregationPipeline.add(new Document(MATCH_OPERATOR, query));
-            }
-
-            projectedKeys = new ArrayList<>();
-            singleResult = true;
-
-            Document projectObject = new Document();
-
-            Document groupByObject = new Document();
-            groupByObject.put(MongoEntityPersister.MONGO_ID_FIELD, 0);
-            Document additionalGroupBy = null;
-
-            for (Projection projection : projectionList) {
-                ProjectionHandler projectionHandler = projectProjectionHandlers.get(projection.getClass());
-                ProjectedProperty projectedProperty = new ProjectedProperty();
-                projectedProperty.projection = projection;
-                if (projection instanceof PropertyProjection) {
-                    PropertyProjection propertyProjection = (PropertyProjection) projection;
-                    String propertyName = propertyProjection.getPropertyName();
-
-                    PersistentProperty property = entity.getPropertyByName(propertyName);
-                    if (property != null) {
-                        projectedProperty.property = property;
-                    } else if (!propertyName.contains(".")) {
-                        throw new InvalidDataAccessResourceUsageException("Attempt to project on a non-existent project [" + propertyName + "]");
-                    }
-                }
-                if (projectionHandler != null) {
-                    singleResult = false;
-
-                    String aggregationKey = projectionHandler.handle(entity, projectObject, groupByObject, projection);
-                    aggregationKey = "id." + aggregationKey;
-                    projectedProperty.projectionKey = aggregationKey;
-                    projectedKeys.add(projectedProperty);
-                } else {
-
-                    projectionHandler = groupByProjectionHandlers.get(projection.getClass());
-                    if (projectionHandler != null) {
-                        projectedProperty.projectionKey = projectionHandler.handle(entity, projectObject, groupByObject, projection);
-                        projectedKeys.add(projectedProperty);
-
-                        if (projection instanceof CountDistinctProjection) {
-                            Document finalCount = new Document(MongoEntityPersister.MONGO_ID_FIELD, 1);
-                            finalCount.put(projectedProperty.projectionKey, new Document(SUM_OPERATOR, 1));
-                            additionalGroupBy = new Document(GROUP_OPERATOR, finalCount);
-                        }
-                    }
-
-                }
-            }
-
-            if (!projectObject.isEmpty()) {
-                aggregationPipeline.add(new Document(PROJECT_OPERATOR, projectObject));
-            }
-
-            aggregationPipeline.add(new Document(GROUP_OPERATOR, groupByObject));
-
-            if (additionalGroupBy != null) {
-                aggregationPipeline.add(additionalGroupBy);
-            }
-
-            List<Order> orderBy = mongoQuery.getOrderBy();
-            if (!orderBy.isEmpty()) {
-                Document sortBy = new Document();
-                for (Order order : orderBy) {
-                    String prop = order.getProperty();
-                    String sortKey = prop;
-                    for (ProjectedProperty pp : projectedKeys) {
-                        if (pp.property != null && pp.property.getName().equals(prop)) {
-                            sortKey = pp.projectionKey;
-                            if (sortKey.startsWith("id.")) {
-                                sortKey = MongoEntityPersister.MONGO_ID_FIELD + "." + sortKey.substring(3);
-                            }
-                            break;
-                        }
-                    }
-                    sortBy.put(sortKey, order.getDirection() == Order.Direction.ASC ? 1 : -1);
-                }
-                aggregationPipeline.add(new Document(SORT_OPERATOR, sortBy));
-            }
-
-            int max = mongoQuery.max != null ? mongoQuery.max : -1;
-            if (max > 0) {
-                aggregationPipeline.add(new Document("$limit", max));
-            }
-            int offset = mongoQuery.offset != null ? mongoQuery.offset : 0;
-            if (offset > 0) {
-                aggregationPipeline.add(new Document("$skip", offset));
-            }
-
-            return this;
-        }
-    }
-}


(grails-core) 09/14: Convert grails-data-hibernate7-spring-orm batch 3 to Groovy

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-java-to-groovy
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit bcfb4ec51458cb6e68775cad7e7fb8320c4be98b
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Sep 8 21:32:39 2026 -0500

    Convert grails-data-hibernate7-spring-orm batch 3 to Groovy
    
    Converts HibernateOperations and LocalSessionFactoryBean to @CompileStatic
    Groovy, preserving the upstream Spring copyright headers verbatim.
    
    Verified with codenarcMain/checkstyleMain on spring-orm and the full
    grails-data-hibernate7-core, grails-data-hibernate7 and
    grails-data-hibernate7-spring-boot test suites.
    
    Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E5dsQNQNLuSdFph84RRQdr
---
 ...eOperations.java => HibernateOperations.groovy} | 128 ++++---
 ...oryBean.java => LocalSessionFactoryBean.groovy} | 370 ++++++++++-----------
 2 files changed, 225 insertions(+), 273 deletions(-)

diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOperations.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOperations.groovy
similarity index 93%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOperations.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOperations.groovy
index d805b29f87..b1a1c0fd7a 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOperations.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/HibernateOperations.groovy
@@ -13,19 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.grails.orm.hibernate.support.hibernate7
 
-package org.grails.orm.hibernate.support.hibernate7;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.List;
-
-import org.hibernate.Filter;
-import org.hibernate.LockMode;
-import org.hibernate.ReplicationMode;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.dao.DataAccessException;
+import groovy.transform.CompileStatic
+import org.hibernate.Filter
+import org.hibernate.LockMode
+import org.hibernate.ReplicationMode
+import org.springframework.dao.DataAccessException
 
 /**
  * Interface that specifies a common set of Hibernate operations as well as
@@ -59,7 +53,8 @@ import org.springframework.dao.DataAccessException;
  * @see org.hibernate.Session
  * @see HibernateTransactionManager
  */
-public interface HibernateOperations {
+@CompileStatic
+interface HibernateOperations {
 
     /**
      * Execute the action specified by the given action object within a
@@ -80,9 +75,7 @@ public interface HibernateOperations {
      * @see HibernateTransactionManager
      * @see org.hibernate.Session
      */
-    @Nullable
-    <T> T execute(HibernateCallback<T> action) throws DataAccessException;
-
+    def <T> T execute(HibernateCallback<T> action) throws DataAccessException
 
     //-------------------------------------------------------------------------
     // Convenience methods for loading individual objects
@@ -102,8 +95,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#get(Class, Object)
      */
-    @Nullable
-    <T> T get(Class<T> entityClass, Serializable id) throws DataAccessException;
+    def <T> T get(Class<T> entityClass, Serializable id) throws DataAccessException
 
     /**
      * Return the persistent instance of the given entity class
@@ -121,8 +113,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#get(Class, Object, org.hibernate.LockOptions)
      */
-    @Nullable
-    <T> T get(Class<T> entityClass, Serializable id, LockMode lockMode) throws DataAccessException;
+    def <T> T get(Class<T> entityClass, Serializable id, LockMode lockMode) throws DataAccessException
 
     /**
      * Return the persistent instance of the given entity class
@@ -137,8 +128,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#get(String, Object)
      */
-    @Nullable
-    Object get(String entityName, Serializable id) throws DataAccessException;
+    Object get(String entityName, Serializable id) throws DataAccessException
 
     /**
      * Return the persistent instance of the given entity class
@@ -155,8 +145,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#get(String, Object, org.hibernate.LockOptions)
      */
-    @Nullable
-    Object get(String entityName, Serializable id, LockMode lockMode) throws DataAccessException;
+    Object get(String entityName, Serializable id, LockMode lockMode) throws DataAccessException
 
     /**
      * Return the persistent instance of the given entity class
@@ -173,7 +162,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#getReference(Class, Object)
      */
-    <T> T load(Class<T> entityClass, Serializable id) throws DataAccessException;
+    def <T> T load(Class<T> entityClass, Serializable id) throws DataAccessException
 
     /**
      * Return the persistent instance of the given entity class
@@ -192,7 +181,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#get(Class, Object, org.hibernate.LockOptions)
      */
-    <T> T load(Class<T> entityClass, Serializable id, LockMode lockMode) throws DataAccessException;
+    def <T> T load(Class<T> entityClass, Serializable id, LockMode lockMode) throws DataAccessException
 
     /**
      * Return the persistent instance of the given entity class
@@ -208,7 +197,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#getReference(String, Object)
      */
-    Object load(String entityName, Serializable id) throws DataAccessException;
+    Object load(String entityName, Serializable id) throws DataAccessException
 
     /**
      * Return the persistent instance of the given entity class
@@ -226,7 +215,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#get(String, Object, org.hibernate.LockOptions)
      */
-    Object load(String entityName, Serializable id, LockMode lockMode) throws DataAccessException;
+    Object load(String entityName, Serializable id, LockMode lockMode) throws DataAccessException
 
     /**
      * Load the persistent instance with the given identifier
@@ -241,7 +230,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#getIdentifier(Object)
      */
-    void load(Object entity, Serializable id) throws DataAccessException;
+    void load(Object entity, Serializable id) throws DataAccessException
 
     /**
      * Re-read the state of the given persistent instance.
@@ -249,7 +238,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#refresh(Object)
      */
-    void refresh(Object entity) throws DataAccessException;
+    void refresh(Object entity) throws DataAccessException
 
     /**
      * Re-read the state of the given persistent instance.
@@ -259,7 +248,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#refresh(Object, org.hibernate.LockOptions)
      */
-    void refresh(Object entity, LockMode lockMode) throws DataAccessException;
+    void refresh(Object entity, LockMode lockMode) throws DataAccessException
 
     /**
      * Check whether the given object is in the Session cache.
@@ -268,7 +257,7 @@ public interface HibernateOperations {
      * @throws DataAccessException if there is a Hibernate error
      * @see org.hibernate.Session#contains(Object)
      */
-    boolean contains(Object entity) throws DataAccessException;
+    boolean contains(Object entity) throws DataAccessException
 
     /**
      * Remove the given object from the {@link org.hibernate.Session} cache.
@@ -276,7 +265,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#evict(Object)
      */
-    void evict(Object entity) throws DataAccessException;
+    void evict(Object entity) throws DataAccessException
 
     /**
      * Force initialization of a Hibernate proxy or persistent collection.
@@ -285,7 +274,7 @@ public interface HibernateOperations {
      * because it is not associated with an active Session
      * @see org.hibernate.Hibernate#initialize(Object)
      */
-    void initialize(Object proxy) throws DataAccessException;
+    void initialize(Object proxy) throws DataAccessException
 
     /**
      * Return an enabled Hibernate {@link Filter} for the given filter name.
@@ -296,8 +285,7 @@ public interface HibernateOperations {
      * @throws IllegalStateException if we are not running within a
      * transactional Session (in which case this operation does not make sense)
      */
-    Filter enableFilter(String filterName) throws IllegalStateException;
-
+    Filter enableFilter(String filterName) throws IllegalStateException
 
     //-------------------------------------------------------------------------
     // Convenience methods for storing individual objects
@@ -312,8 +300,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#lock(Object, LockMode)
      */
-    @SuppressWarnings("checkstyle:EmptyLineSeparator")
-    void lock(Object entity, LockMode lockMode) throws DataAccessException;
+    void lock(Object entity, LockMode lockMode) throws DataAccessException
 
     /**
      * Obtain the specified lock level upon the given object, implicitly
@@ -325,7 +312,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#lock(Object, LockMode)
      */
-    void lock(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+    void lock(String entityName, Object entity, LockMode lockMode) throws DataAccessException
 
     /**
      * Persist the given transient instance.
@@ -334,7 +321,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#persist(Object)
      */
-    Serializable save(Object entity) throws DataAccessException;
+    Serializable save(Object entity) throws DataAccessException
 
     /**
      * Persist the given transient instance.
@@ -344,7 +331,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#persist(String, Object)
      */
-    Serializable save(String entityName, Object entity) throws DataAccessException;
+    Serializable save(String entityName, Object entity) throws DataAccessException
 
     /**
      * Update the given persistent instance,
@@ -353,7 +340,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#merge(Object)
      */
-    void update(Object entity) throws DataAccessException;
+    void update(Object entity) throws DataAccessException
 
     /**
      * Update the given persistent instance,
@@ -366,7 +353,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#merge(Object)
      */
-    void update(Object entity, LockMode lockMode) throws DataAccessException;
+    void update(Object entity, LockMode lockMode) throws DataAccessException
 
     /**
      * Update the given persistent instance,
@@ -376,7 +363,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#merge(String, Object)
      */
-    void update(String entityName, Object entity) throws DataAccessException;
+    void update(String entityName, Object entity) throws DataAccessException
 
     /**
      * Update the given persistent instance,
@@ -390,7 +377,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#merge(String, Object)
      */
-    void update(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+    void update(String entityName, Object entity, LockMode lockMode) throws DataAccessException
 
     /**
      * Save or update the given persistent instance,
@@ -402,7 +389,7 @@ public interface HibernateOperations {
      * @see org.hibernate.Session#persist(Object)
      * @see org.hibernate.Session#merge(Object)
      */
-    void saveOrUpdate(Object entity) throws DataAccessException;
+    void saveOrUpdate(Object entity) throws DataAccessException
 
     /**
      * Save or update the given persistent instance,
@@ -415,7 +402,7 @@ public interface HibernateOperations {
      * @see org.hibernate.Session#persist(String, Object)
      * @see org.hibernate.Session#merge(String, Object)
      */
-    void saveOrUpdate(String entityName, Object entity) throws DataAccessException;
+    void saveOrUpdate(String entityName, Object entity) throws DataAccessException
 
     /**
      * Persist the state of the given detached instance according to the
@@ -425,7 +412,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#replicate(Object, ReplicationMode)
      */
-    void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException;
+    void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException
 
     /**
      * Persist the state of the given detached instance according to the
@@ -436,7 +423,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#replicate(String, Object, ReplicationMode)
      */
-    void replicate(String entityName, Object entity, ReplicationMode replicationMode) throws DataAccessException;
+    void replicate(String entityName, Object entity, ReplicationMode replicationMode) throws DataAccessException
 
     /**
      * Persist the given transient instance. Follows JSR-220 semantics.
@@ -447,7 +434,7 @@ public interface HibernateOperations {
      * @see org.hibernate.Session#persist(Object)
      * @see #save
      */
-    void persist(Object entity) throws DataAccessException;
+    void persist(Object entity) throws DataAccessException
 
     /**
      * Persist the given transient instance. Follows JSR-220 semantics.
@@ -459,7 +446,7 @@ public interface HibernateOperations {
      * @see org.hibernate.Session#persist(String, Object)
      * @see #save
      */
-    void persist(String entityName, Object entity) throws DataAccessException;
+    void persist(String entityName, Object entity) throws DataAccessException
 
     /**
      * Copy the state of the given object onto the persistent object
@@ -479,7 +466,7 @@ public interface HibernateOperations {
      * @see org.hibernate.Session#merge(Object)
      * @see #saveOrUpdate
      */
-    <T> T merge(T entity) throws DataAccessException;
+    def <T> T merge(T entity) throws DataAccessException
 
     /**
      * Copy the state of the given object onto the persistent object
@@ -500,7 +487,7 @@ public interface HibernateOperations {
      * @see org.hibernate.Session#merge(String, Object)
      * @see #saveOrUpdate
      */
-    <T> T merge(String entityName, T entity) throws DataAccessException;
+    def <T> T merge(String entityName, T entity) throws DataAccessException
 
     /**
      * Delete the given persistent instance.
@@ -508,7 +495,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#remove(Object)
      */
-    void delete(Object entity) throws DataAccessException;
+    void delete(Object entity) throws DataAccessException
 
     /**
      * Delete the given persistent instance.
@@ -520,7 +507,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#remove(Object)
      */
-    void delete(Object entity, LockMode lockMode) throws DataAccessException;
+    void delete(Object entity, LockMode lockMode) throws DataAccessException
 
     /**
      * Delete the given persistent instance.
@@ -529,7 +516,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#remove(Object)
      */
-    void delete(String entityName, Object entity) throws DataAccessException;
+    void delete(String entityName, Object entity) throws DataAccessException
 
     /**
      * Delete the given persistent instance.
@@ -542,7 +529,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#remove(Object)
      */
-    void delete(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+    void delete(String entityName, Object entity, LockMode lockMode) throws DataAccessException
 
     /**
      * Delete all given persistent instances.
@@ -552,7 +539,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#remove(Object)
      */
-    void deleteAll(Collection<?> entities) throws DataAccessException;
+    void deleteAll(Collection<?> entities) throws DataAccessException
 
     /**
      * Flush all pending saves, updates and deletes to the database.
@@ -563,7 +550,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#flush()
      */
-    void flush() throws DataAccessException;
+    void flush() throws DataAccessException
 
     /**
      * Remove all objects from the {@link org.hibernate.Session} cache, and
@@ -571,8 +558,7 @@ public interface HibernateOperations {
      * @throws DataAccessException in case of Hibernate errors
      * @see org.hibernate.Session#clear()
      */
-    void clear() throws DataAccessException;
-
+    void clear() throws DataAccessException
 
     //-------------------------------------------------------------------------
     // Convenience finder methods for HQL strings
@@ -590,7 +576,7 @@ public interface HibernateOperations {
      * lambda code block passed to the general {@link #execute} method
      */
     @Deprecated
-    List<?> find(String queryString, Object... values) throws DataAccessException;
+    List<?> find(String queryString, Object... values) throws DataAccessException
 
     /**
      * Execute an HQL query, binding one value to a ":" named parameter
@@ -605,7 +591,7 @@ public interface HibernateOperations {
      * lambda code block passed to the general {@link #execute} method
      */
     @Deprecated
-    List<?> findByNamedParam(String queryString, String paramName, Object value) throws DataAccessException;
+    List<?> findByNamedParam(String queryString, String paramName, Object value) throws DataAccessException
 
     /**
      * Execute an HQL query, binding a number of values to ":" named
@@ -620,7 +606,7 @@ public interface HibernateOperations {
      * lambda code block passed to the general {@link #execute} method
      */
     @Deprecated
-    List<?> findByNamedParam(String queryString, String[] paramNames, Object[] values) throws DataAccessException;
+    List<?> findByNamedParam(String queryString, String[] paramNames, Object[] values) throws DataAccessException
 
     /**
      * Execute an HQL query, binding the properties of the given bean to
@@ -635,8 +621,7 @@ public interface HibernateOperations {
      * lambda code block passed to the general {@link #execute} method
      */
     @Deprecated
-    List<?> findByValueBean(String queryString, Object valueBean) throws DataAccessException;
-
+    List<?> findByValueBean(String queryString, Object valueBean) throws DataAccessException
 
     //-------------------------------------------------------------------------
     // Convenience finder methods for named queries
@@ -655,7 +640,7 @@ public interface HibernateOperations {
      * lambda code block passed to the general {@link #execute} method
      */
     @Deprecated
-    List<?> findByNamedQuery(String queryName, Object... values) throws DataAccessException;
+    List<?> findByNamedQuery(String queryName, Object... values) throws DataAccessException
 
     /**
      * Execute a named query, binding one value to a ":" named parameter
@@ -672,7 +657,7 @@ public interface HibernateOperations {
      */
     @Deprecated
     List<?> findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
-            throws DataAccessException;
+            throws DataAccessException
 
     /**
      * Execute a named query, binding a number of values to ":" named
@@ -689,7 +674,7 @@ public interface HibernateOperations {
      */
     @Deprecated
     List<?> findByNamedQueryAndNamedParam(String queryName, String[] paramNames, Object[] values)
-            throws DataAccessException;
+            throws DataAccessException
 
     /**
      * Execute a named query, binding the properties of the given bean to
@@ -705,8 +690,7 @@ public interface HibernateOperations {
      * lambda code block passed to the general {@link #execute} method
      */
     @Deprecated
-    List<?> findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException;
-
+    List<?> findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException
 
     //-------------------------------------------------------------------------
     // Convenience query methods for iteration and bulk updates/deletes
@@ -725,6 +709,6 @@ public interface HibernateOperations {
      * lambda code block passed to the general {@link #execute} method
      */
     @Deprecated
-    int bulkUpdate(String queryString, Object... values) throws DataAccessException;
+    int bulkUpdate(String queryString, Object... values) throws DataAccessException
 
 }
diff --git a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBean.java b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBean.groovy
similarity index 67%
rename from grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBean.java
rename to grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBean.groovy
index 0421b87bf0..64a516fe92 100644
--- a/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBean.java
+++ b/grails-data-hibernate7/spring-orm/src/main/groovy/org/grails/orm/hibernate/support/hibernate7/LocalSessionFactoryBean.groovy
@@ -13,45 +13,39 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.grails.orm.hibernate.support.hibernate7;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Properties;
-
-import javax.sql.DataSource;
-
-import org.hibernate.Interceptor;
-import org.hibernate.SessionFactory;
-import org.hibernate.boot.MetadataSources;
-import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
-import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
-import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
-import org.hibernate.cache.spi.RegionFactory;
-import org.hibernate.cfg.Configuration;
-import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
-import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
-import org.hibernate.integrator.spi.Integrator;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.BeanFactoryAware;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.FactoryBean;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.SmartInitializingSingleton;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.context.ResourceLoaderAware;
-import org.springframework.core.InfrastructureProxy;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.ResourceLoader;
-import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
-import org.springframework.core.io.support.ResourcePatternResolver;
-import org.springframework.core.io.support.ResourcePatternUtils;
-import org.springframework.core.task.AsyncTaskExecutor;
-import org.springframework.core.type.filter.TypeFilter;
+package org.grails.orm.hibernate.support.hibernate7
+
+import javax.sql.DataSource
+
+import groovy.transform.CompileStatic
+import org.hibernate.Interceptor
+import org.hibernate.SessionFactory
+import org.hibernate.boot.MetadataSources
+import org.hibernate.boot.model.naming.ImplicitNamingStrategy
+import org.hibernate.boot.model.naming.PhysicalNamingStrategy
+import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder
+import org.hibernate.cache.spi.RegionFactory
+import org.hibernate.cfg.Configuration
+import org.hibernate.context.spi.CurrentTenantIdentifierResolver
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider
+import org.hibernate.integrator.spi.Integrator
+import org.springframework.beans.factory.BeanFactory
+import org.springframework.beans.factory.BeanFactoryAware
+import org.springframework.beans.factory.DisposableBean
+import org.springframework.beans.factory.FactoryBean
+import org.springframework.beans.factory.InitializingBean
+import org.springframework.beans.factory.SmartInitializingSingleton
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
+import org.springframework.context.ResourceLoaderAware
+import org.springframework.core.InfrastructureProxy
+import org.springframework.core.io.ClassPathResource
+import org.springframework.core.io.Resource
+import org.springframework.core.io.ResourceLoader
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver
+import org.springframework.core.io.support.ResourcePatternResolver
+import org.springframework.core.io.support.ResourcePatternUtils
+import org.springframework.core.task.AsyncTaskExecutor
+import org.springframework.core.type.filter.TypeFilter
 
 /**
  * {@link FactoryBean} that creates a Hibernate {@link SessionFactory}. This is the usual
@@ -75,89 +69,64 @@ import org.springframework.core.type.filter.TypeFilter;
  * @see LocalSessionFactoryBuilder
  * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
  */
-public class LocalSessionFactoryBean extends HibernateExceptionTranslator
+@CompileStatic
+class LocalSessionFactoryBean extends HibernateExceptionTranslator
         implements FactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware,
         InitializingBean, SmartInitializingSingleton, DisposableBean {
 
-    @Nullable
-    private DataSource dataSource;
+    private DataSource dataSource
 
-    @Nullable
-    private Resource[] configLocations;
+    private Resource[] configLocations
 
-    @Nullable
-    private String[] mappingResources;
+    private String[] mappingResources
 
-    @Nullable
-    private Resource[] mappingLocations;
+    private Resource[] mappingLocations
 
-    @Nullable
-    private Resource[] cacheableMappingLocations;
+    private Resource[] cacheableMappingLocations
 
-    @Nullable
-    private Resource[] mappingJarLocations;
+    private Resource[] mappingJarLocations
 
-    @Nullable
-    private Resource[] mappingDirectoryLocations;
+    private Resource[] mappingDirectoryLocations
 
-    @Nullable
-    private Interceptor entityInterceptor;
+    private Interceptor entityInterceptor
 
-    @Nullable
-    private ImplicitNamingStrategy implicitNamingStrategy;
+    private ImplicitNamingStrategy implicitNamingStrategy
 
-    @Nullable
-    private PhysicalNamingStrategy physicalNamingStrategy;
+    private PhysicalNamingStrategy physicalNamingStrategy
 
-    @Nullable
-    private Object jtaTransactionManager;
+    private Object jtaTransactionManager
 
-    @Nullable
-    private RegionFactory cacheRegionFactory;
+    private RegionFactory cacheRegionFactory
 
-    @Nullable
-    private MultiTenantConnectionProvider multiTenantConnectionProvider;
+    private MultiTenantConnectionProvider multiTenantConnectionProvider
 
-    @Nullable
-    private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
+    private CurrentTenantIdentifierResolver currentTenantIdentifierResolver
 
-    @Nullable
-    private Properties hibernateProperties;
+    private Properties hibernateProperties
 
-    @Nullable
-    private TypeFilter[] entityTypeFilters;
+    private TypeFilter[] entityTypeFilters
 
-    @Nullable
-    private Class<?>[] annotatedClasses;
+    private Class<?>[] annotatedClasses
 
-    @Nullable
-    private String[] annotatedPackages;
+    private String[] annotatedPackages
 
-    @Nullable
-    private String[] packagesToScan;
+    private String[] packagesToScan
 
-    @Nullable
-    private AsyncTaskExecutor bootstrapExecutor;
+    private AsyncTaskExecutor bootstrapExecutor
 
-    @Nullable
-    private Integrator[] hibernateIntegrators;
+    private Integrator[] hibernateIntegrators
 
-    private boolean metadataSourcesAccessed = false;
+    private boolean metadataSourcesAccessed = false
 
-    @Nullable
-    private MetadataSources metadataSources;
+    private MetadataSources metadataSources
 
-    @Nullable
-    private ResourcePatternResolver resourcePatternResolver;
+    private ResourcePatternResolver resourcePatternResolver
 
-    @Nullable
-    private ConfigurableListableBeanFactory beanFactory;
+    private ConfigurableListableBeanFactory beanFactory
 
-    @Nullable
-    private Configuration configuration;
+    private Configuration configuration
 
-    @Nullable
-    private SessionFactory sessionFactory;
+    private SessionFactory sessionFactory
 
     /**
      * Set the DataSource to be used by the SessionFactory.
@@ -165,8 +134,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * <p>If this is set, the Hibernate settings should not define
      * a connection provider to avoid meaningless double configuration.
      */
-    public void setDataSource(DataSource dataSource) {
-        this.dataSource = dataSource;
+    void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource
     }
 
     /**
@@ -176,8 +145,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * resources are specified locally via this bean.
      * @see Configuration#configure(java.net.URL)
      */
-    public void setConfigLocation(Resource configLocation) {
-        this.configLocations = new Resource[] {configLocation};
+    void setConfigLocation(Resource configLocation) {
+        this.configLocations = [configLocation] as Resource[]
     }
 
     /**
@@ -187,8 +156,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * resources are specified locally via this bean.
      * @see Configuration#configure(java.net.URL)
      */
-    public void setConfigLocations(Resource... configLocations) {
-        this.configLocations = configLocations;
+    void setConfigLocations(Resource... configLocations) {
+        this.configLocations = configLocations
     }
 
     /**
@@ -201,8 +170,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @see #setMappingLocations
      * @see Configuration#addResource
      */
-    public void setMappingResources(String... mappingResources) {
-        this.mappingResources = mappingResources;
+    void setMappingResources(String... mappingResources) {
+        this.mappingResources = mappingResources
     }
 
     /**
@@ -214,8 +183,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * or to specify all mappings locally.
      * @see Configuration#addInputStream
      */
-    public void setMappingLocations(Resource... mappingLocations) {
-        this.mappingLocations = mappingLocations;
+    void setMappingLocations(Resource... mappingLocations) {
+        this.mappingLocations = mappingLocations
     }
 
     /**
@@ -227,8 +196,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * or to specify all mappings locally.
      * @see Configuration#addCacheableFile(File)
      */
-    public void setCacheableMappingLocations(Resource... cacheableMappingLocations) {
-        this.cacheableMappingLocations = cacheableMappingLocations;
+    void setCacheableMappingLocations(Resource... cacheableMappingLocations) {
+        this.cacheableMappingLocations = cacheableMappingLocations
     }
 
     /**
@@ -238,8 +207,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * or to specify all mappings locally.
      * @see Configuration#addJar(File)
      */
-    public void setMappingJarLocations(Resource... mappingJarLocations) {
-        this.mappingJarLocations = mappingJarLocations;
+    void setMappingJarLocations(Resource... mappingJarLocations) {
+        this.mappingJarLocations = mappingJarLocations
     }
 
     /**
@@ -249,8 +218,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * or to specify all mappings locally.
      * @see Configuration#addDirectory(File)
      */
-    public void setMappingDirectoryLocations(Resource... mappingDirectoryLocations) {
-        this.mappingDirectoryLocations = mappingDirectoryLocations;
+    void setMappingDirectoryLocations(Resource... mappingDirectoryLocations) {
+        this.mappingDirectoryLocations = mappingDirectoryLocations
     }
 
     /**
@@ -259,24 +228,24 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * Will get applied to any new Session created by this factory.
      * @see Configuration#setInterceptor
      */
-    public void setEntityInterceptor(Interceptor entityInterceptor) {
-        this.entityInterceptor = entityInterceptor;
+    void setEntityInterceptor(Interceptor entityInterceptor) {
+        this.entityInterceptor = entityInterceptor
     }
 
     /**
      * Set a Hibernate 5 {@link ImplicitNamingStrategy} for the SessionFactory.
      * @see Configuration#setImplicitNamingStrategy
      */
-    public void setImplicitNamingStrategy(ImplicitNamingStrategy implicitNamingStrategy) {
-        this.implicitNamingStrategy = implicitNamingStrategy;
+    void setImplicitNamingStrategy(ImplicitNamingStrategy implicitNamingStrategy) {
+        this.implicitNamingStrategy = implicitNamingStrategy
     }
 
     /**
      * Set a Hibernate 5 {@link PhysicalNamingStrategy} for the SessionFactory.
      * @see Configuration#setPhysicalNamingStrategy
      */
-    public void setPhysicalNamingStrategy(PhysicalNamingStrategy physicalNamingStrategy) {
-        this.physicalNamingStrategy = physicalNamingStrategy;
+    void setPhysicalNamingStrategy(PhysicalNamingStrategy physicalNamingStrategy) {
+        this.physicalNamingStrategy = physicalNamingStrategy
     }
 
     /**
@@ -285,8 +254,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * if any. Implicitly sets up {@code JtaPlatform}.
      * @see LocalSessionFactoryBuilder#setJtaTransactionManager
      */
-    public void setJtaTransactionManager(Object jtaTransactionManager) {
-        this.jtaTransactionManager = jtaTransactionManager;
+    void setJtaTransactionManager(Object jtaTransactionManager) {
+        this.jtaTransactionManager = jtaTransactionManager
     }
 
     /**
@@ -297,8 +266,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @since 5.1
      * @see LocalSessionFactoryBuilder#setCacheRegionFactory
      */
-    public void setCacheRegionFactory(RegionFactory cacheRegionFactory) {
-        this.cacheRegionFactory = cacheRegionFactory;
+    void setCacheRegionFactory(RegionFactory cacheRegionFactory) {
+        this.cacheRegionFactory = cacheRegionFactory
     }
 
     /**
@@ -306,16 +275,16 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @since 4.3
      * @see LocalSessionFactoryBuilder#setMultiTenantConnectionProvider
      */
-    public void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
-        this.multiTenantConnectionProvider = multiTenantConnectionProvider;
+    void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
+        this.multiTenantConnectionProvider = multiTenantConnectionProvider
     }
 
     /**
      * Set a {@link CurrentTenantIdentifierResolver} to be passed on to the SessionFactory.
      * @see LocalSessionFactoryBuilder#setCurrentTenantIdentifierResolver
      */
-    public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
-        this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
+    void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
+        this.currentTenantIdentifierResolver = currentTenantIdentifierResolver
     }
 
     /**
@@ -325,19 +294,19 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * provider settings and use a Spring-set DataSource instead.
      * @see #setDataSource
      */
-    public void setHibernateProperties(Properties hibernateProperties) {
-        this.hibernateProperties = hibernateProperties;
+    void setHibernateProperties(Properties hibernateProperties) {
+        this.hibernateProperties = hibernateProperties
     }
 
     /**
      * Return the Hibernate properties, if any. Mainly available for
      * configuration through property paths that specify individual keys.
      */
-    public Properties getHibernateProperties() {
+    Properties getHibernateProperties() {
         if (this.hibernateProperties == null) {
-            this.hibernateProperties = new Properties();
+            this.hibernateProperties = new Properties()
         }
-        return this.hibernateProperties;
+        return this.hibernateProperties
     }
 
     /**
@@ -347,16 +316,16 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * or {@code @jakarta.persistence.MappedSuperclass}.
      * @see #setPackagesToScan
      */
-    public void setEntityTypeFilters(TypeFilter... entityTypeFilters) {
-        this.entityTypeFilters = entityTypeFilters;
+    void setEntityTypeFilters(TypeFilter... entityTypeFilters) {
+        this.entityTypeFilters = entityTypeFilters
     }
 
     /**
      * Specify annotated entity classes to register with this Hibernate SessionFactory.
      * @see Configuration#addAnnotatedClass(Class)
      */
-    public void setAnnotatedClasses(Class<?>... annotatedClasses) {
-        this.annotatedClasses = annotatedClasses;
+    void setAnnotatedClasses(Class<?>... annotatedClasses) {
+        this.annotatedClasses = annotatedClasses
     }
 
     /**
@@ -364,8 +333,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * annotation metadata will be read.
      * @see Configuration#addPackage(String)
      */
-    public void setAnnotatedPackages(String... annotatedPackages) {
-        this.annotatedPackages = annotatedPackages;
+    void setAnnotatedPackages(String... annotatedPackages) {
+        this.annotatedPackages = annotatedPackages
     }
 
     /**
@@ -373,8 +342,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * classpath. This is analogous to Spring's component-scan feature
      * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
      */
-    public void setPackagesToScan(String... packagesToScan) {
-        this.packagesToScan = packagesToScan;
+    void setPackagesToScan(String... packagesToScan) {
+        this.packagesToScan = packagesToScan
     }
 
     /**
@@ -392,8 +361,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @since 4.3
      * @see LocalSessionFactoryBuilder#buildSessionFactory(AsyncTaskExecutor)
      */
-    public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
-        this.bootstrapExecutor = bootstrapExecutor;
+    void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
+        this.bootstrapExecutor = bootstrapExecutor
     }
 
     /**
@@ -405,8 +374,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @see #setMetadataSources
      * @see BootstrapServiceRegistryBuilder#applyIntegrator
      */
-    public void setHibernateIntegrators(Integrator... hibernateIntegrators) {
-        this.hibernateIntegrators = hibernateIntegrators;
+    void setHibernateIntegrators(Integrator... hibernateIntegrators) {
+        this.hibernateIntegrators = hibernateIntegrators
     }
 
     /**
@@ -417,9 +386,9 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @see MetadataSources#MetadataSources(ServiceRegistry)
      * @see BootstrapServiceRegistryBuilder#build()
      */
-    public void setMetadataSources(MetadataSources metadataSources) {
-        this.metadataSourcesAccessed = true;
-        this.metadataSources = metadataSources;
+    void setMetadataSources(MetadataSources metadataSources) {
+        this.metadataSourcesAccessed = true
+        this.metadataSources = metadataSources
     }
 
     /**
@@ -430,21 +399,21 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @since 4.3
      * @see LocalSessionFactoryBuilder#LocalSessionFactoryBuilder(DataSource, ResourceLoader, MetadataSources)
      */
-    public MetadataSources getMetadataSources() {
-        this.metadataSourcesAccessed = true;
+    MetadataSources getMetadataSources() {
+        this.metadataSourcesAccessed = true
         if (this.metadataSources == null) {
-            BootstrapServiceRegistryBuilder builder = new BootstrapServiceRegistryBuilder();
+            BootstrapServiceRegistryBuilder builder = new BootstrapServiceRegistryBuilder()
             if (this.resourcePatternResolver != null) {
-                builder = builder.applyClassLoader(this.resourcePatternResolver.getClassLoader());
+                builder = builder.applyClassLoader(this.resourcePatternResolver.getClassLoader())
             }
             if (this.hibernateIntegrators != null) {
                 for (Integrator integrator : this.hibernateIntegrators) {
-                    builder = builder.applyIntegrator(integrator);
+                    builder = builder.applyIntegrator(integrator)
                 }
             }
-            this.metadataSources = new MetadataSources(builder.build());
+            this.metadataSources = new MetadataSources(builder.build())
         }
-        return this.metadataSources;
+        return this.metadataSources
     }
 
     /**
@@ -452,8 +421,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @param resourceLoader the ResourceLoader to use (never {@code null})
      */
     @Override
-    public void setResourceLoader(ResourceLoader resourceLoader) {
-        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
+    void setResourceLoader(ResourceLoader resourceLoader) {
+        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
     }
 
     /**
@@ -461,11 +430,11 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @return the ResourceLoader to use (never {@code null})
      * @since 4.3
      */
-    public ResourceLoader getResourceLoader() {
+    ResourceLoader getResourceLoader() {
         if (this.resourcePatternResolver == null) {
-            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
+            this.resourcePatternResolver = new PathMatchingResourcePatternResolver()
         }
-        return this.resourcePatternResolver;
+        return this.resourcePatternResolver
     }
 
     /**
@@ -477,132 +446,132 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * @see LocalSessionFactoryBuilder#setBeanContainer
      */
     @Override
-    public void setBeanFactory(BeanFactory beanFactory) {
-        if (beanFactory instanceof ConfigurableListableBeanFactory clbf) {
-            this.beanFactory = clbf;
+    void setBeanFactory(BeanFactory beanFactory) {
+        if (beanFactory instanceof ConfigurableListableBeanFactory) {
+            this.beanFactory = (ConfigurableListableBeanFactory) beanFactory
         }
     }
 
     @Override
-    public void afterPropertiesSet() throws IOException {
+    void afterPropertiesSet() throws IOException {
         if (this.metadataSources != null && !this.metadataSourcesAccessed) {
             // Repeated initialization with no user-customized MetadataSources -> clear it.
-            this.metadataSources = null;
+            this.metadataSources = null
         }
 
         LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(
-                this.dataSource, getResourceLoader(), getMetadataSources());
+                this.dataSource, getResourceLoader(), getMetadataSources())
 
         if (this.configLocations != null) {
             for (Resource resource : this.configLocations) {
                 // Load Hibernate configuration from given location.
-                sfb.configure(resource.getURL());
+                sfb.configure(resource.getURL())
             }
         }
 
         if (this.mappingResources != null) {
             // Register given Hibernate mapping definitions, contained in resource files.
             for (String mapping : this.mappingResources) {
-                Resource mr = new ClassPathResource(mapping.trim(), getResourceLoader().getClassLoader());
-                sfb.addInputStream(mr.getInputStream());
+                Resource mr = new ClassPathResource(mapping.trim(), getResourceLoader().getClassLoader())
+                sfb.addInputStream(mr.getInputStream())
             }
         }
 
         if (this.mappingLocations != null) {
             // Register given Hibernate mapping definitions, contained in resource files.
             for (Resource resource : this.mappingLocations) {
-                sfb.addInputStream(resource.getInputStream());
+                sfb.addInputStream(resource.getInputStream())
             }
         }
 
         if (this.cacheableMappingLocations != null) {
             // Register given cacheable Hibernate mapping definitions, read from the file system.
             for (Resource resource : this.cacheableMappingLocations) {
-                sfb.addCacheableFile(resource.getFile());
+                sfb.addCacheableFile(resource.getFile())
             }
         }
 
         if (this.mappingJarLocations != null) {
             // Register given Hibernate mapping definitions, contained in jar files.
             for (Resource resource : this.mappingJarLocations) {
-                sfb.addJar(resource.getFile());
+                sfb.addJar(resource.getFile())
             }
         }
 
         if (this.mappingDirectoryLocations != null) {
             // Register all Hibernate mapping definitions in the given directories.
             for (Resource resource : this.mappingDirectoryLocations) {
-                File file = resource.getFile();
+                File file = resource.getFile()
                 if (!file.isDirectory()) {
                     throw new IllegalArgumentException(
-                            "Mapping directory location [" + resource + "] does not denote a directory");
+                            'Mapping directory location [' + resource + '] does not denote a directory')
                 }
-                sfb.addDirectory(file);
+                sfb.addDirectory(file)
             }
         }
 
         if (this.entityInterceptor != null) {
-            sfb.setInterceptor(this.entityInterceptor);
+            sfb.setInterceptor(this.entityInterceptor)
         }
 
         if (this.implicitNamingStrategy != null) {
-            sfb.setImplicitNamingStrategy(this.implicitNamingStrategy);
+            sfb.setImplicitNamingStrategy(this.implicitNamingStrategy)
         }
 
         if (this.physicalNamingStrategy != null) {
-            sfb.setPhysicalNamingStrategy(this.physicalNamingStrategy);
+            sfb.setPhysicalNamingStrategy(this.physicalNamingStrategy)
         }
 
         if (this.jtaTransactionManager != null) {
-            sfb.setJtaTransactionManager(this.jtaTransactionManager);
+            sfb.setJtaTransactionManager(this.jtaTransactionManager)
         }
 
         if (this.beanFactory != null) {
-            sfb.setBeanContainer(this.beanFactory);
+            sfb.setBeanContainer(this.beanFactory)
         }
 
         if (this.cacheRegionFactory != null) {
-            sfb.setCacheRegionFactory(this.cacheRegionFactory);
+            sfb.setCacheRegionFactory(this.cacheRegionFactory)
         }
 
         if (this.multiTenantConnectionProvider != null) {
-            sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
+            sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider)
         }
 
         if (this.currentTenantIdentifierResolver != null) {
-            sfb.setCurrentTenantIdentifierResolver(this.currentTenantIdentifierResolver);
+            sfb.setCurrentTenantIdentifierResolver(this.currentTenantIdentifierResolver)
         }
 
         if (this.hibernateProperties != null) {
-            sfb.addProperties(this.hibernateProperties);
+            sfb.addProperties(this.hibernateProperties)
         }
 
         if (this.entityTypeFilters != null) {
-            sfb.setEntityTypeFilters(this.entityTypeFilters);
+            sfb.setEntityTypeFilters(this.entityTypeFilters)
         }
 
         if (this.annotatedClasses != null) {
-            sfb.addAnnotatedClasses(this.annotatedClasses);
+            sfb.addAnnotatedClasses(this.annotatedClasses)
         }
 
         if (this.annotatedPackages != null) {
-            sfb.addPackages(this.annotatedPackages);
+            sfb.addPackages(this.annotatedPackages)
         }
 
         if (this.packagesToScan != null) {
-            sfb.scanPackages(this.packagesToScan);
+            sfb.scanPackages(this.packagesToScan)
         }
 
         // Build SessionFactory instance.
-        this.configuration = sfb;
-        this.sessionFactory = buildSessionFactory(sfb);
+        this.configuration = sfb
+        this.sessionFactory = buildSessionFactory(sfb)
     }
 
     @Override
-    public void afterSingletonsInstantiated() {
+    void afterSingletonsInstantiated() {
         // Enforce completion of asynchronous Hibernate initialization before context refresh completion.
-        if (this.sessionFactory instanceof InfrastructureProxy proxy) {
-            proxy.getWrappedObject();
+        if (this.sessionFactory instanceof InfrastructureProxy) {
+            ((InfrastructureProxy) this.sessionFactory).getWrappedObject()
         }
     }
 
@@ -619,7 +588,7 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      */
     protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
         return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
-                sfb.buildSessionFactory());
+                sfb.buildSessionFactory())
     }
 
     /**
@@ -627,33 +596,32 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
      * Allows for access to configuration metadata stored there (rarely needed).
      * @throws IllegalStateException if the Configuration object has not been initialized yet
      */
-    public final Configuration getConfiguration() {
+    final Configuration getConfiguration() {
         if (this.configuration == null) {
-            throw new IllegalStateException("Configuration not initialized yet");
+            throw new IllegalStateException('Configuration not initialized yet')
         }
-        return this.configuration;
+        return this.configuration
     }
 
     @Override
-    @Nullable
-    public SessionFactory getObject() {
-        return this.sessionFactory;
+    SessionFactory getObject() {
+        return this.sessionFactory
     }
 
     @Override
-    public Class<?> getObjectType() {
-        return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
+    Class<?> getObjectType() {
+        return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory)
     }
 
     @Override
-    public boolean isSingleton() {
-        return true;
+    boolean isSingleton() {
+        return true
     }
 
     @Override
-    public void destroy() {
+    void destroy() {
         if (this.sessionFactory != null) {
-            this.sessionFactory.close();
+            this.sessionFactory.close()
         }
     }