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:25 UTC

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

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;
-    }
-}