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/02/16 00:14:07 UTC

(grails-core) branch 8.0.x-hibernate7 updated (a5b37f1c7e -> cbbc30c78c)

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

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


    from a5b37f1c7e Refactor binders to support custom ID generator creation in SimpleValueBinder
     new 837e88c9cc Refactor GrailsDomainBinder to improve utility instance reuse
     new a9d4b5fdbf Refactor defaultColumnNameFetcher in GrailsDomainBinder to be a local variable
     new c2e74db397 Refactor binders in GrailsDomainBinder to be local variables
     new ef4765f1ae Refactor CollectionHolder to take MetadataBuildingContext in constructor
     new fd8f3ba117 Refactor second pass classes to remove GrailsDomainBinder dependency
     new 31424104eb Refactor GrailsDomainBinder to use local variables for remaining binders and utilities
     new 20762d8e0d Refactor multi-tenant filter binding to dedicated class
     new c034b6c49c Refactor joined subclass binding to a dedicated binder class
     new 5052e3777f Refactor union subclass binding to a dedicated binder class
     new 1c4d710990 Refactor single table subclass binding to a dedicated binder class
     new 9e8c94925e Cleanup redundant polymorphic logic in GrailsDomainBinder
     new 875d23b941 Refactor bindRoot to use entity.isTablePerHierarchy() and cleanup polymorphic logic
     new 72c693743e Encapsulate table abstractness logic in GrailsHibernatePersistentEntity
     new 34866ea7fa Refactor natural identifier binding to support composite primary keys and fix mapping DSL configuration for natural identifiers.
     new 3e2f7d5e5f Refactor NaturalId to return Optional<UniqueKey> and delegate side effects to the binder.
     new cbbc30c78c Refactor class properties binding logic from GrailsDomainBinder to ClassPropertiesBinder and clean up binder signatures.

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


Summary of changes:
 grails-data-hibernate7/core/build.gradle           |   1 +
 .../orm/hibernate/cfg/CompositeIdentity.groovy     |  17 +
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 361 ++++-----------------
 .../cfg/GrailsHibernatePersistentEntity.java       |  14 +
 .../org/grails/orm/hibernate/cfg/Identity.groovy   |   3 +-
 .../org/grails/orm/hibernate/cfg/NaturalId.groovy  |  31 ++
 .../binder/ClassPropertiesBinder.java              |  56 ++++
 .../cfg/domainbinding/binder/CollectionBinder.java |  14 +-
 .../domainbinding/binder/JoinedSubClassBinder.java |  93 ++++++
 .../binder/NaturalIdentifierBinder.java            |  34 +-
 .../domainbinding/binder/SimpleValueBinder.java    |   1 -
 .../binder/SingleTableSubclassBinder.java          |  40 +++
 .../domainbinding/binder/UnionSubclassBinder.java  |  65 ++++
 .../collectionType/BagCollectionType.java          |   7 +-
 .../collectionType/CollectionHolder.java           |  14 +-
 .../collectionType/CollectionType.java             |   6 +-
 .../collectionType/ListCollectionType.java         |   7 +-
 .../collectionType/MapCollectionType.java          |   7 +-
 .../collectionType/SetCollectionType.java          |   7 +-
 .../collectionType/SortedSetCollectionType.java    |   7 +-
 .../domainbinding/hibernate/HibernateIdentity.java |  12 +
 .../hibernate/HibernateMappingBuilder.groovy       |  46 +--
 .../domainbinding/secondpass/ListSecondPass.java   |   8 +-
 .../domainbinding/secondpass/MapSecondPass.java    |   8 +-
 .../domainbinding/secondpass/SetSecondPass.java    |  12 +-
 .../util/MultiTenantFilterBinder.java              | 115 +++++++
 .../grails/orm/hibernate/cfg/NaturalIdSpec.groovy  |  93 ++++++
 .../cfg/domainbinding/CollectionBinderSpec.groovy  | 113 ++++++-
 .../CollectionSecondPassBinderSpec.groovy          | 116 ++++++-
 .../domainbinding/GrailsPropertyBinderSpec.groovy  | 143 ++++++--
 .../domainbinding/ListSecondPassBinderSpec.groovy  | 116 ++++++-
 .../domainbinding/MapSecondPassBinderSpec.groovy   | 116 ++++++-
 .../NaturalIdentifierBinderSpec.groovy             | 119 ++-----
 .../binder/ClassPropertiesBinderSpec.groovy        |  57 ++++
 .../binder/JoinedSubClassBinderSpec.groovy         |  86 +++++
 .../binder/SingleTableSubclassBinderSpec.groovy    |  61 ++++
 .../binder/UnionSubclassBinderSpec.groovy          |  66 ++++
 .../util/MultiTenantFilterBinderSpec.groovy        | 168 ++++++++++
 38 files changed, 1713 insertions(+), 527 deletions(-)
 create mode 100644 grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinder.java
 create mode 100644 grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinder.java
 create mode 100644 grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinder.java
 create mode 100644 grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinder.java
 create mode 100644 grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java
 create mode 100644 grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/NaturalIdSpec.groovy
 create mode 100644 grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinderSpec.groovy
 create mode 100644 grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinderSpec.groovy
 create mode 100644 grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinderSpec.groovy
 create mode 100644 grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinderSpec.groovy
 create mode 100644 grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy


(grails-core) 01/16: Refactor GrailsDomainBinder to improve utility instance reuse

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

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

commit 837e88c9cc0263be04453f7fa93149ed2ce2c952
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Feb 14 13:11:19 2026 -0600

    Refactor GrailsDomainBinder to improve utility instance reuse
    
    - Convert backticksRemover to a local variable in the contribute method.
    - Introduce columnNameForPropertyAndPathFetcher field to consolidate column name fetching logic.
    - Update addMultiTenantFilterIfNecessary and bindJoinedSubClass to use shared fetcher instances instead of creating new ones.
---
 .../org/grails/orm/hibernate/cfg/GrailsDomainBinder.java       | 10 +++++-----
 .../hibernate/cfg/domainbinding/binder/SimpleValueBinder.java  |  1 -
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 8f61097728..7db17c814e 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -140,7 +140,7 @@ public class GrailsDomainBinder
     private IdentityBinder identityBinder;
     private VersionBinder versionBinder;
     private DefaultColumnNameFetcher defaultColumnNameFetcher;
-    private BackticksRemover backticksRemover;
+    private ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher;
 
 
     public JdbcEnvironment getJdbcEnvironment() {
@@ -195,11 +195,11 @@ public class GrailsDomainBinder
                 metadataCollector
                 , rootMappingDefaults
         );
-        this.backticksRemover = new BackticksRemover();
+        BackticksRemover backticksRemover = new BackticksRemover();
         PersistentEntityNamingStrategy namingStrategy = getNamingStrategy();
         JdbcEnvironment jdbcEnvironment = getJdbcEnvironment();
         this.defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover);
-        ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover);
+        this.columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover);
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment);
         EnumTypeBinder enumTypeBinderToUse = enumTypeBinder != null ? enumTypeBinder : new EnumTypeBinder();
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher();
@@ -343,7 +343,7 @@ public class GrailsDomainBinder
             TenantId tenantId = entity.getTenantId();
 
             if (tenantId != null) {
-                String filterCondition = entity.getMultiTenantFilterCondition(new DefaultColumnNameFetcher(getNamingStrategy()));
+                String filterCondition = entity.getMultiTenantFilterCondition(defaultColumnNameFetcher);
 
                 persistentClass.addFilter(
                         GormProperties.TENANT_IDENTITY,
@@ -480,7 +480,7 @@ public class GrailsDomainBinder
         SimpleValue key = new DependantValue(metadataBuildingContext, mytable, joinedSubclass.getIdentifier());
         joinedSubclass.setKey(key);
         var identifier = sub.getIdentity();
-        String columnName = new ColumnNameForPropertyAndPathFetcher(getNamingStrategy(), defaultColumnNameFetcher, backticksRemover).getColumnNameForPropertyAndPath(identifier, EMPTY_PATH, null);
+        String columnName = columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(identifier, EMPTY_PATH, null);
         new SimpleValueColumnBinder().bindSimpleValue(key, identifier.getType().getName(), columnName, false);
 
         joinedSubclass.createPrimaryKey();
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java
index dcdb8b47ae..9f6ded1bda 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java
@@ -32,7 +32,6 @@ public class SimpleValueBinder {
     private final JdbcEnvironment jdbcEnvironment;
     private final GrailsSequenceWrapper grailsSequenceWrapper;
 
-    private static final String SEQUENCE_KEY = GrailsSequenceGeneratorEnum.SEQUENCE.toString();
 
     /**
      * Public constructor that accepts all collaborators.


(grails-core) 09/16: Refactor union subclass binding to a dedicated binder class

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

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

commit 5052e3777fe4706c843f8b295ad7416d4a50dd39
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 11:45:38 2026 -0600

    Refactor union subclass binding to a dedicated binder class
    
    - Create UnionSubclassBinder to handle table-per-concrete-class mapping.
    - Update GrailsDomainBinder to use UnionSubclassBinder as a local dependency.
    - Add UnionSubclassBinderSpec using real entity classes for comprehensive testing.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 48 ++++------------
 .../domainbinding/binder/UnionSubclassBinder.java  | 65 +++++++++++++++++++++
 .../binder/UnionSubclassBinderSpec.groovy          | 66 ++++++++++++++++++++++
 3 files changed, 143 insertions(+), 36 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index b53197732b..677c20ade4 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -40,6 +40,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.NaturalIdentifierBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder;
+import org.grails.orm.hibernate.cfg.domainbinding.binder.UnionSubclassBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.VersionBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder;
@@ -230,12 +231,13 @@ public class GrailsDomainBinder
         VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new);
         MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder();
         JoinedSubClassBinder joinedSubClassBinder = new JoinedSubClassBinder(metadataBuildingContext, namingStrategy, new SimpleValueColumnBinder(), columnNameForPropertyAndPathFetcher, classBinder);
+        UnionSubclassBinder unionSubclassBinder = new UnionSubclassBinder(metadataBuildingContext, namingStrategy, classBinder);
 
         hibernateMappingContext
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder));
     }
 
 
@@ -266,7 +268,7 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
@@ -283,7 +285,7 @@ public class GrailsDomainBinder
                 bindDiscriminatorProperty(root.getTable(), root, m);
             }
             // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder));
+            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder));
         }
 
         multiTenantFilterBinder.addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
@@ -313,9 +315,9 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder) {
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder) {
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder);
 
 
         parent.addSubclass(subClass);
@@ -326,11 +328,11 @@ public class GrailsDomainBinder
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
@@ -340,13 +342,13 @@ public class GrailsDomainBinder
         }
         else if(m.isTablePerConcreteClass()) {
             var union  = new UnionSubclass(parent, this.metadataBuildingContext);
-            bindUnionSubclass(subEntity,  union, mappings, sessionFactoryBeanName, grailsPropertyBinder, classBinder, propertyFromValueCreator);
+            unionSubclassBinder.bindUnionSubclass(subEntity, union, mappings);
             subClass = union;
         }
         else {
             var singleTableSubclass = new SingleTableSubclass(parent, this.metadataBuildingContext);
             singleTableSubclass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
-            bindSubClass(subEntity, singleTableSubclass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder);
+            bindSubClass(subEntity, singleTableSubclass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder);
             subClass = singleTableSubclass;
         }
         subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1));
@@ -361,32 +363,6 @@ public class GrailsDomainBinder
     }
 
 
-    private void bindUnionSubclass(@Nonnull GrailsHibernatePersistentEntity subClass, UnionSubclass unionSubclass,
-                                  @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) throws MappingException {
-        classBinder.bindClass(subClass, unionSubclass, mappings);
-
-        String schema = subClass.getSchema(mappings);
-        String catalog = subClass.getCatalog(mappings);
-
-        Table denormalizedSuperTable = unionSubclass.getSuperclass().getTable();
-        Table mytable = mappings.addDenormalizedTable(
-                schema,
-                catalog,
-                new TableNameFetcher(getNamingStrategy()).getTableName(subClass),
-                Boolean.TRUE.equals(unionSubclass.isAbstract()),
-                null,
-                denormalizedSuperTable, metadataBuildingContext
-        );
-        unionSubclass.setTable( mytable );
-        unionSubclass.setClassName(subClass.getName());
-
-        LOG.info(
-                "Mapping union-subclass: " + unionSubclass.getEntityName() +
-                        " -> " + unionSubclass.getTable().getName()
-        );
-
-
-    }
     /**
      * Binds a sub-class using table-per-hierarchy inheritance mapping
      *
@@ -395,7 +371,7 @@ public class GrailsDomainBinder
      * @param mappings The mappings instance
      */
     private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, SingleTableSubclass subClass, @Nonnull InFlightMetadataCollector mappings,
-                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder) {
+                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder) {
         classBinder.bindClass(sub, subClass, mappings);
 
         if (LOG.isDebugEnabled())
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinder.java
new file mode 100644
index 0000000000..7982ed1005
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinder.java
@@ -0,0 +1,65 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder;
+
+import jakarta.annotation.Nonnull;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
+import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy;
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher;
+import org.hibernate.MappingException;
+import org.hibernate.boot.spi.InFlightMetadataCollector;
+import org.hibernate.boot.spi.MetadataBuildingContext;
+import org.hibernate.mapping.Table;
+import org.hibernate.mapping.UnionSubclass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Binds a union sub-class mapping using table-per-concrete-class
+ *
+ * @since 7.0
+ */
+public class UnionSubclassBinder {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UnionSubclassBinder.class);
+
+    private final MetadataBuildingContext metadataBuildingContext;
+    private final PersistentEntityNamingStrategy namingStrategy;
+    private final ClassBinder classBinder;
+
+    public UnionSubclassBinder(MetadataBuildingContext metadataBuildingContext, PersistentEntityNamingStrategy namingStrategy, ClassBinder classBinder) {
+        this.metadataBuildingContext = metadataBuildingContext;
+        this.namingStrategy = namingStrategy;
+        this.classBinder = classBinder;
+    }
+
+    /**
+     * Binds a union sub-class mapping using table-per-concrete-class
+     *
+     * @param subClass       The Grails sub class
+     * @param unionSubclass  The Hibernate UnionSubclass object
+     * @param mappings       The mappings Object
+     */
+    public void bindUnionSubclass(@Nonnull GrailsHibernatePersistentEntity subClass, UnionSubclass unionSubclass,
+                                   @Nonnull InFlightMetadataCollector mappings) throws MappingException {
+        classBinder.bindClass(subClass, unionSubclass, mappings);
+
+        String schema = subClass.getSchema(mappings);
+        String catalog = subClass.getCatalog(mappings);
+
+        Table denormalizedSuperTable = unionSubclass.getSuperclass().getTable();
+        Table mytable = mappings.addDenormalizedTable(
+                schema,
+                catalog,
+                new TableNameFetcher(namingStrategy).getTableName(subClass),
+                Boolean.TRUE.equals(unionSubclass.isAbstract()),
+                null,
+                denormalizedSuperTable, metadataBuildingContext
+        );
+        unionSubclass.setTable( mytable );
+        unionSubclass.setClassName(subClass.getName());
+
+        if (LOG.isInfoEnabled()) {
+            LOG.info("Mapping union-subclass: " + unionSubclass.getEntityName() +
+                    " -> " + unionSubclass.getTable().getName());
+        }
+    }
+}
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinderSpec.groovy
new file mode 100644
index 0000000000..b9f9da7c92
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinderSpec.groovy
@@ -0,0 +1,66 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder
+
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.Table
+import org.hibernate.mapping.UnionSubclass
+
+/**
+ * Tests for UnionSubclassBinder using real entity classes.
+ */
+class UnionSubclassBinderSpec extends HibernateGormDatastoreSpec {
+
+    UnionSubclassBinder binder
+    ClassBinder classBinder = new ClassBinder()
+
+    void setup() {
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def namingStrategy = getGrailsDomainBinder().getNamingStrategy()
+        binder = new UnionSubclassBinder(buildingContext, namingStrategy, classBinder)
+    }
+
+    void "test bind union subclass with real entities"() {
+        given:
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def mappings = buildingContext.getMetadataCollector()
+        
+        // Register entities in mapping context
+        def rootEntity = createPersistentEntity(UnionSubClassRoot)
+        def subEntity = createPersistentEntity(UnionSubClassSub)
+        
+        // Setup Hibernate RootClass
+        def rootClass = new RootClass(buildingContext)
+        rootClass.setEntityName(UnionSubClassRoot.name)
+        def rootTable = new Table("US_ROOT_TABLE")
+        rootTable.setName("US_ROOT_TABLE")
+        rootClass.setTable(rootTable)
+        
+        // Setup UnionSubclass
+        def unionSubclass = new UnionSubclass(rootClass, buildingContext)
+        unionSubclass.setEntityName(UnionSubClassSub.name)
+
+        when:
+        binder.bindUnionSubclass(subEntity, unionSubclass, mappings)
+
+        then:
+        unionSubclass.getTable() != null
+        unionSubclass.getTable().getName() != "US_ROOT_TABLE"
+        unionSubclass.getClassName() == UnionSubClassSub.name
+    }
+}
+
+@Entity
+class UnionSubClassRoot {
+    Long id
+}
+
+@Entity
+class UnionSubClassSub extends UnionSubClassRoot {
+    String name
+    static mapping = {
+        tablePerHierarchy false
+        tablePerConcreteClass true
+    }
+}


(grails-core) 02/16: Refactor defaultColumnNameFetcher in GrailsDomainBinder to be a local variable

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

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

commit a9d4b5fdbf27a7d7de692f7bb4c6f9edd9cc5756
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Feb 14 13:34:26 2026 -0600

    Refactor defaultColumnNameFetcher in GrailsDomainBinder to be a local variable
    
    - Remove defaultColumnNameFetcher field and convert it to a local variable in the contribute method.
    - Update bindRoot, bindSubClass, createSubclassMapping, and addMultiTenantFilterIfNecessary to accept DefaultColumnNameFetcher as a parameter.
    - Update unit tests to pass DefaultColumnNameFetcher to the bindRoot method.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 30 ++++++++++------------
 .../CollectionSecondPassBinderSpec.groovy          |  4 ++-
 .../domainbinding/GrailsPropertyBinderSpec.groovy  | 10 +++++---
 .../domainbinding/ListSecondPassBinderSpec.groovy  |  4 ++-
 .../domainbinding/MapSecondPassBinderSpec.groovy   |  4 ++-
 5 files changed, 29 insertions(+), 23 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 7db17c814e..89ac71987d 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -139,7 +139,6 @@ public class GrailsDomainBinder
     private CompositeIdBinder compositeIdBinder;
     private IdentityBinder identityBinder;
     private VersionBinder versionBinder;
-    private DefaultColumnNameFetcher defaultColumnNameFetcher;
     private ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher;
 
 
@@ -187,18 +186,17 @@ public class GrailsDomainBinder
 
     @Override
     public void contribute(AdditionalMappingContributions contributions, InFlightMetadataCollector metadataCollector, ResourceStreamLocator resourceStreamLocator, MetadataBuildingContext buildingContext) {
-        RootMappingDefaults rootMappingDefaults = null;
         this.metadataBuildingContext = new MetadataBuildingContextRootImpl(
                 "default",
                 metadataCollector.getBootstrapContext(),
                 metadataCollector.getMetadataBuildingOptions(),
                 metadataCollector
-                , rootMappingDefaults
+                , null
         );
         BackticksRemover backticksRemover = new BackticksRemover();
         PersistentEntityNamingStrategy namingStrategy = getNamingStrategy();
         JdbcEnvironment jdbcEnvironment = getJdbcEnvironment();
-        this.defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover);
+        DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover);
         this.columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover);
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment);
         EnumTypeBinder enumTypeBinderToUse = enumTypeBinder != null ? enumTypeBinder : new EnumTypeBinder();
@@ -263,7 +261,7 @@ public class GrailsDomainBinder
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher));
     }
 
 
@@ -294,7 +292,7 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher) {
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
@@ -311,10 +309,10 @@ public class GrailsDomainBinder
                 bindDiscriminatorProperty(root.getTable(), root, m);
             }
             // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder ));
+            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher ));
         }
 
-        addMultiTenantFilterIfNecessary(entity, root, mappings);
+        addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
 
         mappings.addEntityBinding(root);
     }
@@ -337,7 +335,7 @@ public class GrailsDomainBinder
      */
     private void addMultiTenantFilterIfNecessary(
             @Nonnull GrailsHibernatePersistentEntity entity, PersistentClass persistentClass,
-            @Nonnull InFlightMetadataCollector mappings) {
+            @Nonnull InFlightMetadataCollector mappings, DefaultColumnNameFetcher defaultColumnNameFetcher) {
 
         if (entity.isMultiTenant()) {
             TenantId tenantId = entity.getTenantId();
@@ -382,24 +380,24 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder) {
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher) {
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher);
 
 
         parent.addSubclass(subClass);
         mappings.addEntityBinding(subClass);
 
-        addMultiTenantFilterIfNecessary(sub, subClass, mappings);
+        addMultiTenantFilterIfNecessary(sub, subClass, mappings, defaultColumnNameFetcher);
 
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m) {
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher) {
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
@@ -413,7 +411,7 @@ public class GrailsDomainBinder
         else {
             subClass = new SingleTableSubclass(parent, this.metadataBuildingContext);
             subClass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
-            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName);
+            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher);
         }
         subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1));
         subClass.setDynamicUpdate(m.getDynamicUpdate());
@@ -512,7 +510,7 @@ public class GrailsDomainBinder
      * @param mappings The mappings instance
      */
     private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, Subclass subClass, @Nonnull InFlightMetadataCollector mappings,
-                                String sessionFactoryBeanName) {
+                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher) {
         classBinding.bindClass(sub, subClass, mappings);
 
         if (LOG.isDebugEnabled())
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
index e1df39f439..6af22f62d1 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
@@ -4,6 +4,8 @@ import grails.gorm.annotation.Entity
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
+import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
 
@@ -40,7 +42,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = getPersistentEntity(Book) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory")
+        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
index 18357514fd..dba76ebbe5 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
@@ -18,7 +18,9 @@ import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
 import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
 
 class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
@@ -166,7 +168,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         def authorEntity = createPersistentEntity(ListAuthor)
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory")
+        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
 
         // Manually create RootClass for the main entity to avoid duplicate property binding
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
@@ -206,7 +208,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         def authorEntity = createPersistentEntity(MapAuthor)
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory")
+        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
@@ -243,7 +245,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         def personEntity = getPersistentEntity(org.apache.grails.data.testing.tck.domains.PersonWithCompositeKey) as GrailsHibernatePersistentEntity
         
         when:
-        binder.bindRoot(personEntity, collector, "sessionFactory")
+        binder.bindRoot(personEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
         def rootClass = collector.getEntityBinding(personEntity.name)
 
         then:
@@ -266,7 +268,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = createPersistentEntity(BookForOneToOne) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory")
+        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
 
         // Manually create RootClass for the main entity (AuthorWithOneToOne)
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
index f8ceee2d9b..66d07c71ba 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
@@ -4,6 +4,8 @@ import grails.gorm.annotation.Entity
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
+import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.SimpleValue
@@ -43,7 +45,7 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = getPersistentEntity(ListBinderBook) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory")
+        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
index e4eccb37c9..7d58f13e90 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
@@ -4,6 +4,8 @@ import grails.gorm.annotation.Entity
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
+import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.SimpleValue
@@ -43,7 +45,7 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = getPersistentEntity(MapBookBinder) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory")
+        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())


(grails-core) 07/16: Refactor multi-tenant filter binding to dedicated class

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

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

commit 20762d8e0db9d93d375a0f10eecef313291e9e2e
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Feb 14 22:13:17 2026 -0600

    Refactor multi-tenant filter binding to dedicated class
    
    - Extract filter logic to MultiTenantFilterBinder for better modularity.
    - Improve shouldApplyFilter for Hibernate 7 class and proxy compatibility.
    - Update tests and dependencies to support Hibernate 7 mapping models.
---
 grails-data-hibernate7/core/build.gradle           |   1 +
 .../orm/hibernate/cfg/GrailsDomainBinder.java      |  78 ++--------
 .../util/MultiTenantFilterBinder.java              | 117 ++++++++++++++
 .../util/MultiTenantFilterBinderSpec.groovy        | 168 +++++++++++++++++++++
 4 files changed, 299 insertions(+), 65 deletions(-)

diff --git a/grails-data-hibernate7/core/build.gradle b/grails-data-hibernate7/core/build.gradle
index 9ba23bd2bb..5bf342dcf8 100644
--- a/grails-data-hibernate7/core/build.gradle
+++ b/grails-data-hibernate7/core/build.gradle
@@ -56,6 +56,7 @@ dependencies {
         exclude group:'org.slf4j', module:'slf4j-log4j12'
         exclude group:'xml-apis', module:'xml-apis'
     }
+    api "org.hibernate.models:hibernate-models:1.0.0"
     api 'org.hibernate.validator:hibernate-validator', {
         exclude group:'commons-logging', module:'commons-logging'
         exclude group:'commons-collections', module:'commons-collections'
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index e1588603a8..6392fd1a42 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -45,6 +45,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolde
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher;
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher;
+import org.grails.orm.hibernate.cfg.domainbinding.util.MultiTenantFilterBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover;
 import org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator;
 import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyWrapper;
@@ -226,12 +227,13 @@ public class GrailsDomainBinder
         SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinder);
         IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder);
         VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new);
+        MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder();
 
         hibernateMappingContext
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder));
     }
 
 
@@ -262,7 +264,7 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
@@ -279,10 +281,10 @@ public class GrailsDomainBinder
                 bindDiscriminatorProperty(root.getTable(), root, m);
             }
             // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator ));
+            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder ));
         }
 
-        addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
+        multiTenantFilterBinder.addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
 
         mappings.addEntityBinding(root);
     }
@@ -296,47 +298,6 @@ public class GrailsDomainBinder
         return namingStrategy;
     }
 
-    /**
-     * Add a Hibernate filter for multitenancy if the persistent class is multitenant
-     *
-     * @param entity          target persistent entity for get tenant information
-     * @param persistentClass persistent class for add the filter and get tenant property info
-     * @param mappings        mappings to add the filter
-     */
-    private void addMultiTenantFilterIfNecessary(
-            @Nonnull GrailsHibernatePersistentEntity entity, PersistentClass persistentClass,
-            @Nonnull InFlightMetadataCollector mappings, DefaultColumnNameFetcher defaultColumnNameFetcher) {
-
-        if (entity.isMultiTenant()) {
-            TenantId tenantId = entity.getTenantId();
-
-            if (tenantId != null) {
-                String filterCondition = entity.getMultiTenantFilterCondition(defaultColumnNameFetcher);
-
-                persistentClass.addFilter(
-                        GormProperties.TENANT_IDENTITY,
-                        filterCondition,
-                        true,
-                        Collections.emptyMap(),
-                        Collections.emptyMap()
-                );
-
-                Property property = getProperty(persistentClass, tenantId.getName());
-                if (property.getValue() instanceof BasicValue basicValue) {
-                    JdbcMapping jdbcMapping = basicValue.resolve().getJdbcMapping();
-                    var stringVMap = Collections.singletonMap(GormProperties.TENANT_IDENTITY, jdbcMapping);
-                    FilterDefinition definition = new FilterDefinition(
-                            GormProperties.TENANT_IDENTITY,
-                            filterCondition,
-                            stringVMap
-                    );
-                    mappings.addFilterDefinition(definition);
-                }
-            }
-        }
-    }
-
-
     /**
      * Binds a sub class.
      *
@@ -350,24 +311,24 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder) {
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder);
 
 
         parent.addSubclass(subClass);
         mappings.addEntityBinding(subClass);
 
-        addMultiTenantFilterIfNecessary(sub, subClass, mappings, defaultColumnNameFetcher);
+        multiTenantFilterBinder.addMultiTenantFilterIfNecessary(sub, subClass, mappings, defaultColumnNameFetcher);
 
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
@@ -381,7 +342,7 @@ public class GrailsDomainBinder
         else {
             subClass = new SingleTableSubclass(parent, this.metadataBuildingContext);
             subClass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
-            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator);
+            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder);
         }
         subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1));
         subClass.setDynamicUpdate(m.getDynamicUpdate());
@@ -480,7 +441,7 @@ public class GrailsDomainBinder
      * @param mappings The mappings instance
      */
     private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, Subclass subClass, @Nonnull InFlightMetadataCollector mappings,
-                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
+                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder) {
         classBinder.bindClass(sub, subClass, mappings);
 
         if (LOG.isDebugEnabled())
@@ -653,17 +614,4 @@ public class GrailsDomainBinder
     public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
 
     }
-
-    private Property getProperty(PersistentClass associatedClass, String propertyName) throws MappingException {
-        try {
-            return associatedClass.getProperty(propertyName);
-        }
-        catch (MappingException e) {
-            //maybe it's squirreled away in a composite primary key
-            if (associatedClass.getKey() instanceof Component) {
-                return ((Component) associatedClass.getKey()).getProperty(propertyName);
-            }
-            throw e;
-        }
-    }
 }
\ No newline at end of file
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java
new file mode 100644
index 0000000000..a74d51f0bb
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java
@@ -0,0 +1,117 @@
+package org.grails.orm.hibernate.cfg.domainbinding.util;
+
+import java.util.Collections;
+import java.util.Optional;
+
+import jakarta.annotation.Nonnull;
+
+import org.hibernate.MappingException;
+import org.hibernate.boot.spi.InFlightMetadataCollector;
+import org.hibernate.metamodel.mapping.JdbcMapping;
+import org.hibernate.engine.spi.FilterDefinition;
+import org.hibernate.mapping.BasicValue;
+import org.hibernate.mapping.Component;
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.mapping.Property;
+import org.hibernate.mapping.RootClass;
+
+import org.grails.datastore.mapping.model.config.GormProperties;
+import org.grails.datastore.mapping.model.types.TenantId;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
+
+/**
+ * Utility class for binding multi-tenant filters to the Hibernate meta model.
+ *
+ * @author Walter Duque de Estrada
+ * @author Graeme Rocher
+ * @since 7.0
+ */
+public class MultiTenantFilterBinder {
+
+    /**
+     * Adds a multi-tenant filter to the given persistent class if necessary.
+     *
+     * @param entity          The target persistent entity
+     * @param persistentClass The persistent class to add the filter to
+     * @param mappings        The in-flight metadata collector
+     * @param fetcher         The column name fetcher
+     */
+    public void addMultiTenantFilterIfNecessary(
+            @Nonnull GrailsHibernatePersistentEntity entity,
+            @Nonnull PersistentClass persistentClass,
+            @Nonnull InFlightMetadataCollector mappings,
+            @Nonnull DefaultColumnNameFetcher fetcher) {
+
+        if (!entity.isMultiTenant()) {
+            return;
+        }
+
+        Optional.ofNullable(entity.getTenantId())
+                .map(TenantId::getName)
+                .map(name -> getProperty(persistentClass, name))
+                .ifPresent(property -> {
+                    var filterName = GormProperties.TENANT_IDENTITY;
+                    ensureGlobalFilterDefinition(mappings, filterName, property);
+                    applyFilterToPersistentClass(entity, persistentClass, fetcher, filterName, property);
+                });
+    }
+
+    private void ensureGlobalFilterDefinition(InFlightMetadataCollector mappings, String filterName, Property property) {
+        if (mappings.getFilterDefinition(filterName) == null && property.getValue() instanceof BasicValue basicValue) {
+            JdbcMapping jdbcMapping = basicValue.resolve().getJdbcMapping();
+            mappings.addFilterDefinition(new FilterDefinition(
+                    filterName,
+                    null, // No default condition; let classes specify their own
+                    Collections.singletonMap(filterName, jdbcMapping)
+            ));
+        }
+    }
+
+    private void applyFilterToPersistentClass(
+            GrailsHibernatePersistentEntity entity,
+            PersistentClass persistentClass,
+            DefaultColumnNameFetcher fetcher,
+            String filterName,
+            Property property) {
+
+        if (shouldApplyFilter(entity, persistentClass, property)) {
+            persistentClass.addFilter(
+                    filterName,
+                    entity.getMultiTenantFilterCondition(fetcher),
+                    true, // autoAliasInjection
+                    Collections.emptyMap(),
+                    Collections.emptyMap()
+            );
+        }
+    }
+
+    private boolean shouldApplyFilter(GrailsHibernatePersistentEntity entity, PersistentClass persistentClass, Property property) {
+        boolean isRoot = persistentClass instanceof RootClass || persistentClass.equals(persistentClass.getRootClass());
+        
+        var table = persistentClass.getTable();
+        var propertyValue = property.getValue();
+        var propertyTable = propertyValue != null ? propertyValue.getTable() : null;
+
+        boolean isInherited = table != null && propertyTable != null && !table.equals(propertyTable);
+
+        // Apply if it's the root or if the subclass has its own table containing the column (UnionSubclass).
+        // Skip if it's a SingleTable subclass (redundant) or JoinedSubclass where column is in root (alias safety).
+        if (isRoot || !isInherited) {
+            return isRoot || !entity.isTablePerHierarchySubclass();
+        }
+        return false;
+    }
+
+    private Property getProperty(PersistentClass associatedClass, String propertyName) {
+        try {
+            return associatedClass.getProperty(propertyName);
+        }
+        catch (MappingException e) {
+            // maybe it's squirreled away in a composite primary key
+            if (associatedClass.getKey() instanceof Component component) {
+                return component.getProperty(propertyName);
+            }
+            return null;
+        }
+    }
+}
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy
new file mode 100644
index 0000000000..58de04027b
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy
@@ -0,0 +1,168 @@
+package org.grails.orm.hibernate.cfg.domainbinding.util
+
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.domainbinding.util.MultiTenantFilterBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
+import org.grails.datastore.mapping.model.config.GormProperties
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.hibernate.mapping.BasicValue
+import org.hibernate.mapping.PersistentClass
+import org.hibernate.mapping.Property
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.SingleTableSubclass
+import org.hibernate.mapping.JoinedSubclass
+import org.hibernate.mapping.UnionSubclass
+import org.hibernate.mapping.Table
+import org.hibernate.metamodel.mapping.JdbcMapping
+import org.hibernate.engine.spi.FilterDefinition
+import org.grails.datastore.mapping.model.types.TenantId
+import spock.lang.Specification
+
+/**
+ * Tests for MultiTenantFilterBinder.
+ */
+class MultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec {
+
+    MultiTenantFilterBinder filterBinder = new MultiTenantFilterBinder()
+    DefaultColumnNameFetcher fetcher = Mock(DefaultColumnNameFetcher)
+    InFlightMetadataCollector mockCollector = GroovyMock(InFlightMetadataCollector)
+
+    void "test add multi tenant filter to root class"() {
+        given:
+        def entity = Mock(GrailsHibernatePersistentEntity)
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def persistentClass = new RootClass(buildingContext)
+        
+        def tenantId = Mock(TenantId)
+        def property = new Property()
+        property.setName("tenantId")
+        
+        def table = new Table("ROOT_TABLE")
+        def value = new BasicValue(buildingContext, table)
+        value.setTypeName("long")
+        
+        entity.isMultiTenant() >> true
+        entity.getTenantId() >> tenantId
+        tenantId.getName() >> "tenantId"
+        
+        property.setValue(value)
+        persistentClass.setTable(table)
+        persistentClass.addProperty(property)
+        
+        // Setup for FilterDefinition
+        mockCollector.getFilterDefinition(GormProperties.TENANT_IDENTITY) >> null
+        
+        entity.getMultiTenantFilterCondition(fetcher) >> "tenant_id = :tenantId"
+
+        when:
+        filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher)
+
+        then:
+        1 * mockCollector.addFilterDefinition(_ as FilterDefinition)
+        persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY && it.getCondition() == "tenant_id = :tenantId" }
+    }
+
+    void "test skip filter for single table subclass (redundant)"() {
+        given:
+        def entity = Mock(GrailsHibernatePersistentEntity)
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def rootClass = new RootClass(buildingContext)
+        def table = new Table("ROOT_TABLE")
+        rootClass.setTable(table)
+        
+        def persistentClass = new SingleTableSubclass(rootClass, buildingContext)
+        def tenantId = Mock(TenantId)
+        
+        def property = new Property()
+        property.setName("tenantId")
+        def value = new BasicValue(buildingContext, table)
+        value.setTypeName("long")
+        property.setValue(value)
+        
+        rootClass.addProperty(property)
+        
+        entity.isMultiTenant() >> true
+        entity.getTenantId() >> tenantId
+        tenantId.getName() >> "tenantId"
+        
+        entity.isTablePerHierarchySubclass() >> true
+        mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition)
+
+        when:
+        filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher)
+
+        then:
+        !persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY }
+    }
+
+    void "test skip filter for joined subclass if inherited (alias safety)"() {
+        given:
+        def entity = Mock(GrailsHibernatePersistentEntity)
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def rootClass = new RootClass(buildingContext)
+        def rootTable = new Table("ROOT_TABLE")
+        rootTable.setName("ROOT_TABLE")
+        rootClass.setTable(rootTable)
+
+        def persistentClass = new JoinedSubclass(rootClass, buildingContext)
+        def subTable = new Table("SUB_TABLE")
+        subTable.setName("SUB_TABLE")
+        persistentClass.setTable(subTable)
+        
+        def tenantId = Mock(TenantId)
+        def property = new Property()
+        property.setName("tenantId")
+        def value = new BasicValue(buildingContext, rootTable)
+        value.setTypeName("long")
+        property.setValue(value)
+        
+        rootClass.addProperty(property)
+
+        entity.isMultiTenant() >> true
+        entity.getTenantId() >> tenantId
+        tenantId.getName() >> "tenantId"
+        
+        mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition)
+
+        when:
+        filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher)
+
+        then:
+        !persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY }
+    }
+
+    void "test add filter for union subclass (own table)"() {
+        given:
+        def entity = Mock(GrailsHibernatePersistentEntity)
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def rootClass = new RootClass(buildingContext)
+        def subTable = new Table("SUB_TABLE")
+
+        def persistentClass = new UnionSubclass(rootClass, buildingContext)
+        persistentClass.setTable(subTable)
+        
+        def tenantId = Mock(TenantId)
+        def property = new Property()
+        property.setName("tenantId")
+        def value = new BasicValue(buildingContext, subTable)
+        value.setTypeName("long")
+        property.setValue(value)
+        
+        persistentClass.addProperty(property)
+
+        entity.isMultiTenant() >> true
+        entity.getTenantId() >> tenantId
+        tenantId.getName() >> "tenantId"
+        
+        entity.isTablePerHierarchySubclass() >> false
+        mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition)
+        entity.getMultiTenantFilterCondition(fetcher) >> "tenant_id = :tenantId"
+
+        when:
+        filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher)
+
+        then:
+        persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY && it.getCondition() == "tenant_id = :tenantId" }
+    }
+}


(grails-core) 06/16: Refactor GrailsDomainBinder to use local variables for remaining binders and utilities

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

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

commit 31424104eb27777335259e5069c54bee7f6cc240
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Feb 14 18:18:41 2026 -0600

    Refactor GrailsDomainBinder to use local variables for remaining binders and utilities
    
    - Remove classBinding, enumTypeBinder, and propertyFromValueCreator fields from GrailsDomainBinder.
    - Instantiate ClassBinder, EnumTypeBinder, and PropertyFromValueCreator as local variables in the contribute method.
    - Update binding method signatures to propagate these local dependencies.
    - Retain only the 3-argument constructor and initialize MappingCacheHolder within it.
    - Update unit tests to reflect the changed method signatures and removed getters.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 100 ++++++++-------------
 .../domainbinding/secondpass/SetSecondPass.java    |   2 +
 .../cfg/domainbinding/CollectionBinderSpec.groovy  |   3 +-
 .../CollectionSecondPassBinderSpec.groovy          |   3 +-
 .../domainbinding/GrailsPropertyBinderSpec.groovy  |  19 ++--
 .../domainbinding/ListSecondPassBinderSpec.groovy  |   3 +-
 .../domainbinding/MapSecondPassBinderSpec.groovy   |   3 +-
 7 files changed, 57 insertions(+), 76 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 3d35c5a9cc..e1588603a8 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -125,31 +125,22 @@ public class GrailsDomainBinder
     private final String sessionFactoryName;
     private final String dataSourceName;
     private final HibernateMappingContext hibernateMappingContext;
-    private final ClassBinder classBinding;
-    private final EnumTypeBinder enumTypeBinder;
-    private final PropertyFromValueCreator propertyFromValueCreator;
-    private ComponentPropertyBinder componentPropertyBinder;
-    private Closure defaultMapping;
     private PersistentEntityNamingStrategy namingStrategy;
     private MetadataBuildingContext metadataBuildingContext;
-    private MappingCacheHolder mappingCacheHolder;
+    private final MappingCacheHolder mappingCacheHolder;
 
 
     public JdbcEnvironment getJdbcEnvironment() {
         return  metadataBuildingContext.getMetadataCollector().getDatabase().getJdbcEnvironment();
     }
 
-    public GrailsDomainBinder(String dataSourceName
-                            , String sessionFactoryName
-                            , HibernateMappingContext hibernateMappingContext
-                            , ClassBinder classBinding
-                            , EnumTypeBinder enumTypeBinder) {
+    public GrailsDomainBinder(
+            String dataSourceName,
+            String sessionFactoryName,
+            HibernateMappingContext hibernateMappingContext) {
         this.sessionFactoryName = sessionFactoryName;
         this.dataSourceName = dataSourceName;
         this.hibernateMappingContext = hibernateMappingContext;
-        this.classBinding = classBinding;
-        this.enumTypeBinder = enumTypeBinder;
-        this.propertyFromValueCreator = new PropertyFromValueCreator();
         this.mappingCacheHolder = MappingCacheHolder.getInstance();
 
         // pre-build mappings
@@ -161,22 +152,6 @@ public class GrailsDomainBinder
 
 
 
-    public GrailsDomainBinder(
-            String dataSourceName,
-            String sessionFactoryName,
-            HibernateMappingContext hibernateMappingContext) {
-        this(dataSourceName
-                , sessionFactoryName
-                , hibernateMappingContext
-                , new ClassBinder()
-                , new EnumTypeBinder()
-        );
-
-    }
-
-
-
-
     @Override
     public void contribute(AdditionalMappingContributions contributions, InFlightMetadataCollector metadataCollector, ResourceStreamLocator resourceStreamLocator, MetadataBuildingContext buildingContext) {
         this.metadataBuildingContext = new MetadataBuildingContextRootImpl(
@@ -193,7 +168,9 @@ public class GrailsDomainBinder
         DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover);
         ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover);
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment);
-        EnumTypeBinder enumTypeBinderToUse = enumTypeBinder != null ? enumTypeBinder : new EnumTypeBinder();
+        EnumTypeBinder enumTypeBinder = new EnumTypeBinder();
+        PropertyFromValueCreator propertyFromValueCreator = new PropertyFromValueCreator();
+        ClassBinder classBinder = new ClassBinder();
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher();
         CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder = new CompositeIdentifierToManyToOneBinder(
                 new org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator(),
@@ -211,7 +188,7 @@ public class GrailsDomainBinder
                 namingStrategy,
                 jdbcEnvironment,
                 simpleValueBinder,
-                enumTypeBinderToUse,
+                enumTypeBinder,
                 manyToOneBinder,
                 compositeIdentifierToManyToOneBinder,
                 simpleValueColumnFetcher
@@ -222,7 +199,7 @@ public class GrailsDomainBinder
                 jdbcEnvironment,
                 getMappingCacheHolder(),
                 collectionHolder,
-                enumTypeBinderToUse,
+                enumTypeBinder,
                 collectionBinder,
                 propertyFromValueCreator,
                 null,
@@ -235,7 +212,7 @@ public class GrailsDomainBinder
                 metadataBuildingContext,
                 namingStrategy,
                 collectionHolder,
-                enumTypeBinderToUse,
+                enumTypeBinder,
                 componentPropertyBinder,
                 collectionBinder,
                 simpleValueBinder,
@@ -254,7 +231,7 @@ public class GrailsDomainBinder
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator));
     }
 
 
@@ -285,13 +262,13 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder) {
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
         }
         var children = entity.getChildEntities(dataSourceName);
-        RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName, identityBinder, versionBinder, grailsPropertyBinder);
+        RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator);
         if (root.isPolymorphic()) {
             Mapping m = entity.getMappedForm();
             final Mapping finalMapping = m;
@@ -302,7 +279,7 @@ public class GrailsDomainBinder
                 bindDiscriminatorProperty(root.getTable(), root, m);
             }
             // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder ));
+            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator ));
         }
 
         addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
@@ -373,9 +350,9 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder) {
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator);
 
 
         parent.addSubclass(subClass);
@@ -386,25 +363,25 @@ public class GrailsDomainBinder
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder) {
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
             subClass = new JoinedSubclass(parent, this.metadataBuildingContext);
-            bindJoinedSubClass(subEntity, (JoinedSubclass) subClass, mappings, sessionFactoryBeanName, columnNameForPropertyAndPathFetcher, grailsPropertyBinder);
+            bindJoinedSubClass(subEntity, (JoinedSubclass) subClass, mappings, sessionFactoryBeanName, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator);
         }
         else if(m.isTablePerConcreteClass()) {
             subClass = new UnionSubclass(parent, this.metadataBuildingContext);
-            bindUnionSubclass(subEntity, (UnionSubclass) subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
+            bindUnionSubclass(subEntity, (UnionSubclass) subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder, classBinder, propertyFromValueCreator);
         }
         else {
             subClass = new SingleTableSubclass(parent, this.metadataBuildingContext);
             subClass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
-            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder);
+            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator);
         }
         subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1));
         subClass.setDynamicUpdate(m.getDynamicUpdate());
@@ -418,8 +395,8 @@ public class GrailsDomainBinder
 
 
     private void bindUnionSubclass(@Nonnull GrailsHibernatePersistentEntity subClass, UnionSubclass unionSubclass,
-                                  @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder) throws MappingException {
-        classBinding.bindClass(subClass, unionSubclass, mappings);
+                                  @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) throws MappingException {
+        classBinder.bindClass(subClass, unionSubclass, mappings);
 
         String schema = subClass.getSchema(mappings);
         String catalog = subClass.getCatalog(mappings);
@@ -441,7 +418,7 @@ public class GrailsDomainBinder
                         " -> " + unionSubclass.getTable().getName()
         );
 
-        createClassProperties(subClass, unionSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
+        createClassProperties(subClass, unionSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
 
     }
     /**
@@ -453,8 +430,8 @@ public class GrailsDomainBinder
      * @param sessionFactoryBeanName the session factory bean name
      */
     private void bindJoinedSubClass(GrailsHibernatePersistentEntity sub, JoinedSubclass joinedSubclass,
-                                    InFlightMetadataCollector mappings, String sessionFactoryBeanName, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder) {
-        classBinding.bindClass(sub, joinedSubclass, mappings);
+                                    InFlightMetadataCollector mappings, String sessionFactoryBeanName, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
+        classBinder.bindClass(sub, joinedSubclass, mappings);
 
         String schemaName = sub.getSchema(mappings);
         String catalogName = sub.getCatalog(mappings);
@@ -478,7 +455,7 @@ public class GrailsDomainBinder
         joinedSubclass.createForeignKey();
 
         // properties
-        createClassProperties(sub, joinedSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
+        createClassProperties(sub, joinedSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
     }
 
     private String getJoinedSubClassTableName(
@@ -503,15 +480,15 @@ public class GrailsDomainBinder
      * @param mappings The mappings instance
      */
     private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, Subclass subClass, @Nonnull InFlightMetadataCollector mappings,
-                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder) {
-        classBinding.bindClass(sub, subClass, mappings);
+                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
+        classBinder.bindClass(sub, subClass, mappings);
 
         if (LOG.isDebugEnabled())
             LOG.debug("Mapping subclass: " + subClass.getEntityName() +
                     " -> " + subClass.getTable().getName());
 
         // properties
-        createClassProperties(sub, subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
+        createClassProperties(sub, subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
     }
 
     /**
@@ -576,13 +553,15 @@ public class GrailsDomainBinder
                                                        String sessionFactoryBeanName,
                                                        IdentityBinder identityBinder,
                                                        VersionBinder versionBinder,
-                                                       GrailsPropertyBinder grailsPropertyBinder
+                                                       GrailsPropertyBinder grailsPropertyBinder,
+                                                       ClassBinder classBinder,
+                                                       PropertyFromValueCreator propertyFromValueCreator
     ) {
 
         RootClass root = new RootClass(this.metadataBuildingContext);
         root.setAbstract(domainClass.isAbstract());
         root.setPolymorphic(!children.isEmpty());
-        classBinding.bindClass(domainClass, root, mappings);
+        classBinder.bindClass(domainClass, root, mappings);
 
         // get the schema and catalog names from the configuration
         Mapping gormMapping = domainClass.getMappedForm();
@@ -625,7 +604,7 @@ public class GrailsDomainBinder
         identityBinder.bindIdentity(domainClass, root, mappings, gormMapping, sessionFactoryBeanName);
         versionBinder.bindVersion(domainClass.getVersion(), root);
         root.createPrimaryKey();
-        createClassProperties(domainClass, root, mappings, sessionFactoryBeanName, grailsPropertyBinder);
+        createClassProperties(domainClass, root, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
 
         return root;
     }
@@ -641,7 +620,7 @@ public class GrailsDomainBinder
      * @param sessionFactoryBeanName  the session factory bean name
      */
     private void createClassProperties(@Nonnull GrailsHibernatePersistentEntity domainClass, PersistentClass persistentClass,
-                                         @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder) {
+                                         @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder, PropertyFromValueCreator propertyFromValueCreator) {
 
 
 
@@ -664,11 +643,6 @@ public class GrailsDomainBinder
     }
 
 
-    public PropertyFromValueCreator getPropertyFromValueCreator() {
-        return propertyFromValueCreator;
-    }
-
-
     @Override
     public String getContributorName() {
         return "GORM";
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java
index 09cc80dfbc..b3c7c5416c 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java
@@ -1,5 +1,6 @@
 package org.grails.orm.hibernate.cfg.domainbinding.secondpass;
 
+import java.io.Serial;
 import java.util.Map;
 
 import jakarta.annotation.Nonnull;
@@ -19,6 +20,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyPrope
  */
 public class SetSecondPass implements org.hibernate.boot.spi.SecondPass, GrailsSecondPass  {
 
+    @Serial
     private static final long serialVersionUID = -5540526942092611348L;
 
     private final CollectionSecondPassBinder collectionSecondPassBinder;
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
index 84aaf1f6be..9f8b6969c4 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
@@ -17,6 +17,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToMa
 import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ClassBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
 import org.grails.orm.hibernate.cfg.GrailsDomainBinder
 
@@ -116,7 +117,7 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
 
     protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
         def binders = getBinders(binder)
-        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder, new ClassBinder(), new PropertyFromValueCreator())
     }
 
     void setupSpec() {
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
index 2bf547279e..5c4163b15f 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
@@ -26,6 +26,7 @@ import org.grails.orm.hibernate.cfg.GrailsDomainBinder
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPassBinder
 
 import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ClassBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
 import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
 import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
@@ -118,7 +119,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
     protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
         def binders = getBinders(binder)
-        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder, new ClassBinder(), new PropertyFromValueCreator())
     }
 
     void setupSpec() {
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
index e90f4c1a0b..75f29d47b5 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
@@ -12,6 +12,7 @@ import org.hibernate.mapping.SimpleValue
 import org.hibernate.mapping.Value
 
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ClassBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
@@ -122,7 +123,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
 
     protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
         def binders = getBinders(binder)
-        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder, new ClassBinder(), new PropertyFromValueCreator())
     }
 
     void setupSpec() {
@@ -148,7 +149,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def titleProp = persistentEntity.getPropertyByName("title") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, titleProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, titleProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, titleProp))
 
         then:
         Property prop = rootClass.getProperty("title")
@@ -170,7 +171,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def statusProp = persistentEntity.getPropertyByName("status") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, statusProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, statusProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, statusProp))
 
         then:
         Property prop = rootClass.getProperty("status")
@@ -196,7 +197,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def ownerProp = petEntity.getPropertyByName("owner") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, ownerProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, ownerProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, ownerProp))
 
         then:
         Property prop = rootClass.getProperty("owner")
@@ -219,7 +220,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def addressProp = persistentEntity.getPropertyByName("homeAddress") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, addressProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, addressProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, addressProp))
 
         then:
         Property prop = rootClass.getProperty("homeAddress")
@@ -247,7 +248,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def petsProp = personEntity.getPropertyByName("pets") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, petsProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, petsProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, petsProp))
 
         then:
         Property prop = rootClass.getProperty("pets")
@@ -286,7 +287,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def booksProp = authorEntity.getPropertyByName("books") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, booksProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, booksProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, booksProp))
         collector.processSecondPasses(binder.getMetadataBuildingContext())
 
         then:
@@ -325,7 +326,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def booksProp = authorEntity.getPropertyByName("books") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, booksProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, booksProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, booksProp))
         collector.processSecondPasses(binder.getMetadataBuildingContext())
 
         then:
@@ -386,7 +387,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         when:
         def childBookProp = authorEntity.getPropertyByName("childBook") as GrailsHibernatePersistentProperty
         Value value = propertyBinder.bindProperty(rootClass, childBookProp, collector, "sessionFactory")
-        rootClass.addProperty(binder.getPropertyFromValueCreator().createProperty(value, childBookProp))
+        rootClass.addProperty(new PropertyFromValueCreator().createProperty(value, childBookProp))
         // Process second passes to ensure Hibernate's internal mappings are finalized
         collector.processSecondPasses(binder.getMetadataBuildingContext())
 
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
index 1270d45e57..94ce5a94b0 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
@@ -28,6 +28,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPas
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.ListSecondPassBinder
 
 import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ClassBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
 import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
 import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
@@ -120,7 +121,7 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
     protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
         def binders = getBinders(binder)
-        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder, new ClassBinder(), new PropertyFromValueCreator())
     }
 
     void setupSpec() {
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
index e07ffd467f..033e1f3a9f 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
@@ -28,6 +28,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPas
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.MapSecondPassBinder
 
 import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ClassBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
 import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
 import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
@@ -120,7 +121,7 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
     protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
         def binders = getBinders(binder)
-        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder, new ClassBinder(), new PropertyFromValueCreator())
     }
 
     void setupSpec() {


(grails-core) 16/16: Refactor class properties binding logic from GrailsDomainBinder to ClassPropertiesBinder and clean up binder signatures.

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

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

commit cbbc30c78c9d774f7dae6ab3631bec5eafc0b769
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 18:13:51 2026 -0600

    Refactor class properties binding logic from GrailsDomainBinder to ClassPropertiesBinder and clean up binder signatures.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 50 ++++++-------------
 .../binder/ClassPropertiesBinder.java              | 56 +++++++++++++++++++++
 .../binder/ClassPropertiesBinderSpec.groovy        | 57 ++++++++++++++++++++++
 3 files changed, 127 insertions(+), 36 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 11358ac2fc..d64e6b569a 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -18,6 +18,7 @@ import groovy.lang.Closure;
 
 import org.grails.datastore.mapping.model.config.GormProperties;
 import org.grails.datastore.mapping.model.types.TenantId;
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ClassPropertiesBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ClassBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ColumnConfigToColumnBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder;
@@ -230,6 +231,8 @@ public class GrailsDomainBinder
         SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinder);
         IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder);
         VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new);
+        NaturalIdentifierBinder naturalIdentifierBinder = new NaturalIdentifierBinder();
+        ClassPropertiesBinder classPropertiesBinder = new ClassPropertiesBinder(grailsPropertyBinder, propertyFromValueCreator, naturalIdentifierBinder);
         MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder();
         JoinedSubClassBinder joinedSubClassBinder = new JoinedSubClassBinder(metadataBuildingContext, namingStrategy, new SimpleValueColumnBinder(), columnNameForPropertyAndPathFetcher, classBinder);
         UnionSubclassBinder unionSubclassBinder = new UnionSubclassBinder(metadataBuildingContext, namingStrategy, classBinder);
@@ -239,7 +242,7 @@ public class GrailsDomainBinder
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, identityBinder, versionBinder, classBinder, classPropertiesBinder, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
     }
 
 
@@ -270,20 +273,20 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, ClassBinder classBinder, ClassPropertiesBinder classPropertiesBinder, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder, SingleTableSubclassBinder  [...]
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
         }
         var children = entity.getChildEntities(dataSourceName);
-        RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator);
+        RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName, identityBinder, versionBinder, classBinder, classPropertiesBinder);
         Mapping m = entity.getMappedForm();
         final Mapping finalMapping = m;
         if (!children.isEmpty() && entity.isTablePerHierarchy()) {
             bindDiscriminatorProperty(root, m);
         }
         // bind the sub classes
-        children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
+        children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, classBinder, classPropertiesBinder, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
 
         multiTenantFilterBinder.addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
 
@@ -312,9 +315,9 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder, SingleTableSubclassBinder singleTabl [...]
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ClassBinder classBinder, ClassPropertiesBinder classPropertiesBinder, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder, SingleTableSubclassBinder singleTableSubclassBinder) {
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, classBinder, classPropertiesBinder, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder);
 
 
         parent.addSubclass(subClass);
@@ -325,11 +328,11 @@ public class GrailsDomainBinder
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, classBinder, classPropertiesBinder, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ClassBinder classBinder, ClassPropertiesBinder classPropertiesBinder, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder, SingleTableSubclassBind [...]
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
@@ -355,7 +358,7 @@ public class GrailsDomainBinder
         subClass.setAbstract(subEntity.isAbstract());
         subClass.setEntityName(subEntity.getName());
         subClass.setJpaEntityName(GrailsHibernateUtil.unqualify(subEntity.getName()));
-        createClassProperties(subEntity, subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
+        classPropertiesBinder.bindClassProperties(subEntity, subClass, mappings, sessionFactoryBeanName);
         return subClass;
     }
 
@@ -427,9 +430,8 @@ public class GrailsDomainBinder
                                                        String sessionFactoryBeanName,
                                                        IdentityBinder identityBinder,
                                                        VersionBinder versionBinder,
-                                                       GrailsPropertyBinder grailsPropertyBinder,
                                                        ClassBinder classBinder,
-                                                       PropertyFromValueCreator propertyFromValueCreator
+                                                       ClassPropertiesBinder classPropertiesBinder
     ) {
 
         RootClass root = new RootClass(this.metadataBuildingContext);
@@ -475,36 +477,12 @@ public class GrailsDomainBinder
         identityBinder.bindIdentity(domainClass, root, mappings, gormMapping, sessionFactoryBeanName);
         versionBinder.bindVersion(domainClass.getVersion(), root);
         root.createPrimaryKey();
-        createClassProperties(domainClass, root, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
+        classPropertiesBinder.bindClassProperties(domainClass, root, mappings, sessionFactoryBeanName);
 
         return root;
     }
 
 
-    /**
-     * Creates and binds the properties for the specified Grails domain class and PersistentClass
-     * and binds them to the Hibernate runtime meta model
-     *
-     * @param domainClass     The Grails domain class
-     * @param persistentClass The Hibernate PersistentClass instance
-     * @param mappings        The Hibernate Mappings instance
-     * @param sessionFactoryBeanName  the session factory bean name
-     */
-    private void createClassProperties(@Nonnull GrailsHibernatePersistentEntity domainClass, PersistentClass persistentClass,
-                                         @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder, PropertyFromValueCreator propertyFromValueCreator) {
-
-
-
-        for (GrailsHibernatePersistentProperty currentGrailsProp : domainClass.getPersistentPropertiesToBind()) {
-           var value = grailsPropertyBinder.bindProperty(persistentClass, currentGrailsProp, mappings, sessionFactoryBeanName);
-           persistentClass.addProperty(propertyFromValueCreator.createProperty(value, currentGrailsProp));
-        }
-
-        new NaturalIdentifierBinder().bindNaturalIdentifier(domainClass.getMappedForm(), persistentClass);
-    }
-
-
-
     public MetadataBuildingContext getMetadataBuildingContext() {
         return metadataBuildingContext;
     }
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinder.java
new file mode 100644
index 0000000000..f8f32804c4
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinder.java
@@ -0,0 +1,56 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder;
+
+import jakarta.annotation.Nonnull;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty;
+import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator;
+import org.hibernate.boot.spi.InFlightMetadataCollector;
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.mapping.Value;
+
+/**
+ * Binds the properties of a Grails domain class to the Hibernate meta-model.
+ *
+ * @author Graeme Rocher
+ * @since 7.0
+ */
+public class ClassPropertiesBinder {
+
+    private final GrailsPropertyBinder grailsPropertyBinder;
+    private final PropertyFromValueCreator propertyFromValueCreator;
+    private final NaturalIdentifierBinder naturalIdentifierBinder;
+
+    public ClassPropertiesBinder(GrailsPropertyBinder grailsPropertyBinder,
+                                 PropertyFromValueCreator propertyFromValueCreator,
+                                 NaturalIdentifierBinder naturalIdentifierBinder) {
+        this.grailsPropertyBinder = grailsPropertyBinder;
+        this.propertyFromValueCreator = propertyFromValueCreator;
+        this.naturalIdentifierBinder = naturalIdentifierBinder;
+    }
+
+    public ClassPropertiesBinder(GrailsPropertyBinder grailsPropertyBinder,
+                                 PropertyFromValueCreator propertyFromValueCreator) {
+        this(grailsPropertyBinder, propertyFromValueCreator, new NaturalIdentifierBinder());
+    }
+
+    /**
+     * Binds the properties of the specified Grails domain class to the Hibernate meta-model.
+     *
+     * @param domainClass The Grails domain class
+     * @param persistentClass The Hibernate PersistentClass instance
+     * @param mappings The Hibernate InFlightMetadataCollector instance
+     * @param sessionFactoryBeanName The session factory bean name
+     */
+    public void bindClassProperties(@Nonnull GrailsHibernatePersistentEntity domainClass,
+                                    PersistentClass persistentClass,
+                                    @Nonnull InFlightMetadataCollector mappings,
+                                    String sessionFactoryBeanName) {
+
+        for (GrailsHibernatePersistentProperty currentGrailsProp : domainClass.getPersistentPropertiesToBind()) {
+            Value value = grailsPropertyBinder.bindProperty(persistentClass, currentGrailsProp, mappings, sessionFactoryBeanName);
+            persistentClass.addProperty(propertyFromValueCreator.createProperty(value, currentGrailsProp));
+        }
+
+        naturalIdentifierBinder.bindNaturalIdentifier(domainClass.getMappedForm(), persistentClass);
+    }
+}
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinderSpec.groovy
new file mode 100644
index 0000000000..c096318426
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ClassPropertiesBinderSpec.groovy
@@ -0,0 +1,57 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder
+
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty
+import org.grails.orm.hibernate.cfg.Mapping
+import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.hibernate.mapping.Property
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.Value
+
+class ClassPropertiesBinderSpec extends HibernateGormDatastoreSpec {
+
+    void "test bindClassProperties"() {
+        given:
+        def grailsPropertyBinder = Mock(GrailsPropertyBinder)
+        def propertyFromValueCreator = Mock(PropertyFromValueCreator)
+        def naturalIdentifierBinder = Mock(NaturalIdentifierBinder)
+        def binder = new ClassPropertiesBinder(grailsPropertyBinder, propertyFromValueCreator, naturalIdentifierBinder)
+
+        def domainClass = Mock(GrailsHibernatePersistentEntity)
+        def persistentClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        def mappings = Mock(InFlightMetadataCollector)
+        def sessionFactoryBeanName = "sessionFactory"
+
+        def prop1 = Mock(GrailsHibernatePersistentProperty)
+        def prop2 = Mock(GrailsHibernatePersistentProperty)
+        domainClass.getPersistentPropertiesToBind() >> [prop1, prop2]
+        
+        def value1 = Mock(Value)
+        def value2 = Mock(Value)
+        
+        def hibernateProp1 = new Property()
+        hibernateProp1.setName("hibernateProp1")
+        def hibernateProp2 = new Property()
+        hibernateProp2.setName("hibernateProp2")
+        
+        def mapping = Mock(Mapping)
+        domainClass.getMappedForm() >> mapping
+
+        when:
+        binder.bindClassProperties(domainClass, persistentClass, mappings, sessionFactoryBeanName)
+
+        then:
+        1 * grailsPropertyBinder.bindProperty(persistentClass, prop1, mappings, sessionFactoryBeanName) >> value1
+        1 * propertyFromValueCreator.createProperty(value1, prop1) >> hibernateProp1
+
+        1 * grailsPropertyBinder.bindProperty(persistentClass, prop2, mappings, sessionFactoryBeanName) >> value2
+        1 * propertyFromValueCreator.createProperty(value2, prop2) >> hibernateProp2
+
+        persistentClass.getProperty("hibernateProp1") == hibernateProp1
+        persistentClass.getProperty("hibernateProp2") == hibernateProp2
+
+        1 * naturalIdentifierBinder.bindNaturalIdentifier(mapping, persistentClass)
+    }
+}


(grails-core) 11/16: Cleanup redundant polymorphic logic in GrailsDomainBinder

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

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

commit 9e8c94925ed55780238a41f03547a2203bf8a067
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 12:34:16 2026 -0600

    Cleanup redundant polymorphic logic in GrailsDomainBinder
    
    - Remove redundant polymorphic check in bindRoot and use children.isEmpty() check instead.
    - Remove deprecated setPolymorphic calls in bindRootPersistentClassCommonValues and bindDiscriminatorProperty.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 23 +++++++++-------------
 1 file changed, 9 insertions(+), 14 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index e000e4c45d..8ee1eb3815 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -277,18 +277,16 @@ public class GrailsDomainBinder
         }
         var children = entity.getChildEntities(dataSourceName);
         RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator);
-        if (root.isPolymorphic()) {
-            Mapping m = entity.getMappedForm();
-            final Mapping finalMapping = m;
-            boolean tablePerSubclass = !m.getTablePerHierarchy();
-            if (!tablePerSubclass) {
-                // if the root class has children create a discriminator property
-
-                bindDiscriminatorProperty(root.getTable(), root, m);
-            }
-            // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
+        Mapping m = entity.getMappedForm();
+        final Mapping finalMapping = m;
+        boolean tablePerSubclass = !m.getTablePerHierarchy();
+        if (!children.isEmpty() && !tablePerSubclass) {
+            // if the root class has children create a discriminator property
+
+            bindDiscriminatorProperty(root.getTable(), root, m);
         }
+        // bind the sub classes
+        children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
 
         multiTenantFilterBinder.addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
 
@@ -421,8 +419,6 @@ public class GrailsDomainBinder
                 new ColumnConfigToColumnBinder().bindColumnConfigToColumn(c, cc, null);
             }
         }
-
-        entity.setPolymorphic(true);
     }
 
     /*
@@ -441,7 +437,6 @@ public class GrailsDomainBinder
 
         RootClass root = new RootClass(this.metadataBuildingContext);
         root.setAbstract(domainClass.isAbstract());
-        root.setPolymorphic(!children.isEmpty());
         classBinder.bindClass(domainClass, root, mappings);
 
         // get the schema and catalog names from the configuration


(grails-core) 13/16: Encapsulate table abstractness logic in GrailsHibernatePersistentEntity

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

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

commit 72c693743e504db196e2f36a3cd276726347c1f0
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 13:23:52 2026 -0600

    Encapsulate table abstractness logic in GrailsHibernatePersistentEntity
    
    - Add isTablePerConcreteClass() and isTableAbstract() to GrailsHibernatePersistentEntity.
    - Update GrailsDomainBinder.bindRoot to use domainClass.isTableAbstract().
---
 .../groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java  | 4 +---
 .../orm/hibernate/cfg/GrailsHibernatePersistentEntity.java       | 9 +++++++++
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index e10a69e3a8..11358ac2fc 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -459,14 +459,12 @@ public class GrailsDomainBinder
         var catalog = domainClass.getCatalog(mappings);
 
 
-        var isAbstract = !gormMapping.getTablePerHierarchy() && gormMapping.isTablePerConcreteClass() && root.isAbstract();
-
         // create the table
         var table = mappings.addTable(schema
                 , catalog
                 , new TableNameFetcher(getNamingStrategy()).getTableName(domainClass)
                 , null
-                , isAbstract
+                , domainClass.isTableAbstract()
                 , metadataBuildingContext
         );
         root.setTable(table);
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java
index d27f5aa8c0..9ad18b2ba9 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java
@@ -40,6 +40,15 @@ package org.grails.orm.hibernate.cfg;
          Mapping mapping = getMappedForm();
          return mapping == null || mapping.getTablePerHierarchy();
      }
+ 
+     default boolean isTablePerConcreteClass() {
+         Mapping mapping = getMappedForm();
+         return mapping != null && mapping.isTablePerConcreteClass();
+     }
+
+     default boolean isTableAbstract() {
+         return !isTablePerHierarchy() && isTablePerConcreteClass() && isAbstract();
+     }
 
      default boolean isTablePerHierarchySubclass() {
          Mapping rootMapping = getRootMapping();


(grails-core) 04/16: Refactor CollectionHolder to take MetadataBuildingContext in constructor

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

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

commit ef4765f1aec4b89c82f375efb8bc6953a71decb3
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Feb 14 15:55:32 2026 -0600

    Refactor CollectionHolder to take MetadataBuildingContext in constructor
    
    - Update CollectionType and its subclasses to accept MetadataBuildingContext instead of GrailsDomainBinder.
    - Update CollectionHolder constructor to accept MetadataBuildingContext.
    - Remove collectionHolder field and getter from GrailsDomainBinder, making it a local variable in contribute.
    - Update unit tests to reflect these changes.
---
 .../org/grails/orm/hibernate/cfg/GrailsDomainBinder.java   | 12 ++++--------
 .../domainbinding/collectionType/BagCollectionType.java    |  7 +++----
 .../cfg/domainbinding/collectionType/CollectionHolder.java | 14 +++++++-------
 .../cfg/domainbinding/collectionType/CollectionType.java   |  6 ++----
 .../domainbinding/collectionType/ListCollectionType.java   |  7 +++----
 .../domainbinding/collectionType/MapCollectionType.java    |  7 +++----
 .../domainbinding/collectionType/SetCollectionType.java    |  7 +++----
 .../collectionType/SortedSetCollectionType.java            |  7 +++----
 .../cfg/domainbinding/CollectionBinderSpec.groovy          |  6 ++++--
 .../domainbinding/CollectionSecondPassBinderSpec.groovy    |  6 ++++--
 .../cfg/domainbinding/GrailsPropertyBinderSpec.groovy      |  6 ++++--
 .../cfg/domainbinding/ListSecondPassBinderSpec.groovy      |  6 ++++--
 .../cfg/domainbinding/MapSecondPassBinderSpec.groovy       |  6 ++++--
 13 files changed, 48 insertions(+), 49 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 9e37b89f25..c17485f379 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -133,7 +133,6 @@ public class GrailsDomainBinder
     private PersistentEntityNamingStrategy namingStrategy;
     private MetadataBuildingContext metadataBuildingContext;
     private MappingCacheHolder mappingCacheHolder;
-    private CollectionHolder collectionHolder;
 
 
     public JdbcEnvironment getJdbcEnvironment() {
@@ -152,7 +151,7 @@ public class GrailsDomainBinder
         this.enumTypeBinder = enumTypeBinder;
         this.propertyFromValueCreator = new PropertyFromValueCreator();
         this.mappingCacheHolder = MappingCacheHolder.getInstance();
-        this.collectionHolder = new CollectionHolder(this);
+
         // pre-build mappings
         for (GrailsHibernatePersistentEntity persistentEntity : hibernateMappingContext.getHibernatePersistentEntities(dataSourceName)) {
             mappingCacheHolder.cacheMapping(persistentEntity);
@@ -187,6 +186,7 @@ public class GrailsDomainBinder
                 metadataCollector
                 , null
         );
+        CollectionHolder collectionHolder = new CollectionHolder(metadataBuildingContext);
         BackticksRemover backticksRemover = new BackticksRemover();
         PersistentEntityNamingStrategy namingStrategy = getNamingStrategy();
         JdbcEnvironment jdbcEnvironment = getJdbcEnvironment();
@@ -222,7 +222,7 @@ public class GrailsDomainBinder
                 namingStrategy,
                 jdbcEnvironment,
                 getMappingCacheHolder(),
-                getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 collectionBinder,
                 propertyFromValueCreator,
@@ -235,7 +235,7 @@ public class GrailsDomainBinder
         GrailsPropertyBinder grailsPropertyBinder = new GrailsPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
-                getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 componentPropertyBinder,
                 collectionBinder,
@@ -664,10 +664,6 @@ public class GrailsDomainBinder
         return mappingCacheHolder;
     }
 
-    public CollectionHolder getCollectionHolder() {
-        return collectionHolder;
-    }
-
 
     public PropertyFromValueCreator getPropertyFromValueCreator() {
         return propertyFromValueCreator;
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/BagCollectionType.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/BagCollectionType.java
index a598f8fd91..5224382135 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/BagCollectionType.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/BagCollectionType.java
@@ -3,13 +3,12 @@ package org.grails.orm.hibernate.cfg.domainbinding.collectionType;
 import java.util.Collection;
 
 import org.hibernate.mapping.PersistentClass;
-
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
+import org.hibernate.boot.spi.MetadataBuildingContext;
 
 public class BagCollectionType extends CollectionType {
 
-    public BagCollectionType(GrailsDomainBinder binder) {
-        super(Collection.class, binder);
+    public BagCollectionType(MetadataBuildingContext buildingContext) {
+        super(Collection.class, buildingContext);
     }
 
     @Override
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionHolder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionHolder.java
index 1f76cc5f92..36ccbb14ab 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionHolder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionHolder.java
@@ -6,17 +6,17 @@ import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
 
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
+import org.hibernate.boot.spi.MetadataBuildingContext;
 
 public record CollectionHolder(Map<Class<?>, CollectionType> map) {
 
-    public CollectionHolder(GrailsDomainBinder binder) {
+    public CollectionHolder(MetadataBuildingContext buildingContext) {
         this(Map.ofEntries(
-                Map.entry(Set.class, new SetCollectionType(binder)),
-                Map.entry(SortedSet.class, new SetCollectionType(binder)),
-                Map.entry(List.class, new ListCollectionType(binder)),
-                Map.entry(Collection.class, new BagCollectionType(binder)),
-                Map.entry(Map.class, new MapCollectionType(binder))
+                Map.entry(Set.class, new SetCollectionType(buildingContext)),
+                Map.entry(SortedSet.class, new SetCollectionType(buildingContext)),
+                Map.entry(List.class, new ListCollectionType(buildingContext)),
+                Map.entry(Collection.class, new BagCollectionType(buildingContext)),
+                Map.entry(Map.class, new MapCollectionType(buildingContext))
         ));
     }
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionType.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionType.java
index 4fc3c003c9..322e7d9017 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionType.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/CollectionType.java
@@ -16,7 +16,6 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyPrope
 public abstract class CollectionType {
 
     protected final Class<?> clazz;
-    protected final GrailsDomainBinder binder;
     protected final MetadataBuildingContext buildingContext;
 
     public abstract Collection createCollection(PersistentClass owner);
@@ -30,10 +29,9 @@ public abstract class CollectionType {
         return coll;
     }
 
-    protected CollectionType(Class<?> clazz, GrailsDomainBinder binder) {
+    protected CollectionType(Class<?> clazz, MetadataBuildingContext buildingContext) {
         this.clazz = clazz;
-        this.binder = binder;
-        this.buildingContext = binder.getMetadataBuildingContext();
+        this.buildingContext = buildingContext;
     }
 
     @Override
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/ListCollectionType.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/ListCollectionType.java
index bb37465926..1518c42c83 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/ListCollectionType.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/ListCollectionType.java
@@ -4,13 +4,12 @@ import java.util.List;
 
 import org.hibernate.mapping.Collection;
 import org.hibernate.mapping.PersistentClass;
-
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
+import org.hibernate.boot.spi.MetadataBuildingContext;
 
 public class ListCollectionType extends CollectionType {
 
-    public ListCollectionType(GrailsDomainBinder binder) {
-        super(List.class, binder);
+    public ListCollectionType(MetadataBuildingContext buildingContext) {
+        super(List.class, buildingContext);
     }
 
     @Override
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/MapCollectionType.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/MapCollectionType.java
index 1756084fbe..049ea65a35 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/MapCollectionType.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/MapCollectionType.java
@@ -4,13 +4,12 @@ import java.util.Map;
 
 import org.hibernate.mapping.Collection;
 import org.hibernate.mapping.PersistentClass;
-
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
+import org.hibernate.boot.spi.MetadataBuildingContext;
 
 public class MapCollectionType extends CollectionType {
 
-    public MapCollectionType(GrailsDomainBinder binder) {
-        super(Map.class, binder);
+    public MapCollectionType(MetadataBuildingContext buildingContext) {
+        super(Map.class, buildingContext);
     }
 
     @Override
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SetCollectionType.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SetCollectionType.java
index 5b4fc2d121..753bc0ec65 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SetCollectionType.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SetCollectionType.java
@@ -4,13 +4,12 @@ import java.util.Set;
 
 import org.hibernate.mapping.Collection;
 import org.hibernate.mapping.PersistentClass;
-
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
+import org.hibernate.boot.spi.MetadataBuildingContext;
 
 public class SetCollectionType extends CollectionType {
 
-    public SetCollectionType(GrailsDomainBinder binder) {
-        super(Set.class, binder);
+    public SetCollectionType(MetadataBuildingContext buildingContext) {
+        super(Set.class, buildingContext);
     }
 
     @Override
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SortedSetCollectionType.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SortedSetCollectionType.java
index 547e63599b..2c6f32daf5 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SortedSetCollectionType.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/collectionType/SortedSetCollectionType.java
@@ -4,13 +4,12 @@ import java.util.SortedSet;
 
 import org.hibernate.mapping.Collection;
 import org.hibernate.mapping.PersistentClass;
-
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
+import org.hibernate.boot.spi.MetadataBuildingContext;
 
 public class SortedSetCollectionType extends CollectionType {
 
-    public SortedSetCollectionType(GrailsDomainBinder binder) {
-        super(SortedSet.class, binder);
+    public SortedSetCollectionType(MetadataBuildingContext buildingContext) {
+        super(SortedSet.class, buildingContext);
     }
 
     @Override
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
index 31c0bef73a..49e798674e 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
@@ -31,6 +31,7 @@ import org.hibernate.mapping.BasicValue
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder
 import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
 import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
@@ -44,6 +45,7 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
         BackticksRemover backticksRemover = new BackticksRemover()
         DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
         ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        CollectionHolder collectionHolder = new CollectionHolder(metadataBuildingContext)
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
         EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
@@ -74,7 +76,7 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
                 namingStrategy,
                 jdbcEnvironment,
                 binder.getMappingCacheHolder(),
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 collectionBinder,
                 new PropertyFromValueCreator(),
@@ -87,7 +89,7 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
         GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 componentPropertyBinder,
                 collectionBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
index cd46b537a6..b9ad7a642f 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
@@ -6,6 +6,7 @@ import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
 import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
@@ -46,6 +47,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         BackticksRemover backticksRemover = new BackticksRemover()
         DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
         ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        CollectionHolder collectionHolder = new CollectionHolder(metadataBuildingContext)
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
         EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
@@ -76,7 +78,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
                 namingStrategy,
                 jdbcEnvironment,
                 binder.getMappingCacheHolder(),
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 collectionBinder,
                 new PropertyFromValueCreator(),
@@ -89,7 +91,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 componentPropertyBinder,
                 collectionBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
index 4449b59ca1..63b2def531 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
@@ -20,6 +20,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
 import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
 
@@ -50,6 +51,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         BackticksRemover backticksRemover = new BackticksRemover()
         DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
         ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        CollectionHolder collectionHolder = new CollectionHolder(metadataBuildingContext)
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
         EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
@@ -80,7 +82,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
                 namingStrategy,
                 jdbcEnvironment,
                 binder.getMappingCacheHolder(),
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 collectionBinder,
                 new PropertyFromValueCreator(),
@@ -93,7 +95,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 componentPropertyBinder,
                 collectionBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
index 7bf27fe3e4..a850f89cc0 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
@@ -6,6 +6,7 @@ import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
 import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
@@ -48,6 +49,7 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         BackticksRemover backticksRemover = new BackticksRemover()
         DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
         ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        CollectionHolder collectionHolder = new CollectionHolder(metadataBuildingContext)
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
         EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
@@ -78,7 +80,7 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
                 namingStrategy,
                 jdbcEnvironment,
                 binder.getMappingCacheHolder(),
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 collectionBinder,
                 new PropertyFromValueCreator(),
@@ -91,7 +93,7 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 componentPropertyBinder,
                 collectionBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
index 1a921437bb..5803d6508c 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
@@ -6,6 +6,7 @@ import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
 import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
@@ -48,6 +49,7 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         BackticksRemover backticksRemover = new BackticksRemover()
         DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
         ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        CollectionHolder collectionHolder = new CollectionHolder(metadataBuildingContext)
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
         EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
@@ -78,7 +80,7 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
                 namingStrategy,
                 jdbcEnvironment,
                 binder.getMappingCacheHolder(),
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 collectionBinder,
                 new PropertyFromValueCreator(),
@@ -91,7 +93,7 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
-                binder.getCollectionHolder(),
+                collectionHolder,
                 enumTypeBinderToUse,
                 componentPropertyBinder,
                 collectionBinder,


(grails-core) 15/16: Refactor NaturalId to return Optional and delegate side effects to the binder.

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

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

commit 3e2f7d5e5fc70fee9c83e7cdff1ecd44cca97d5e
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 17:48:04 2026 -0600

    Refactor NaturalId to return Optional<UniqueKey> and delegate side effects to the binder.
---
 .../org/grails/orm/hibernate/cfg/NaturalId.groovy  |  31 +++++
 .../binder/NaturalIdentifierBinder.java            |  29 +---
 .../grails/orm/hibernate/cfg/NaturalIdSpec.groovy  |  93 +++++++++++++
 .../NaturalIdentifierBinderSpec.groovy             | 148 +++------------------
 4 files changed, 151 insertions(+), 150 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy
index 852d254033..dbc8ffdf04 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy
@@ -18,6 +18,9 @@ package org.grails.orm.hibernate.cfg
 import groovy.transform.CompileStatic
 import groovy.transform.builder.Builder
 import groovy.transform.builder.SimpleStrategy
+import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator
+import org.hibernate.mapping.PersistentClass
+import org.hibernate.mapping.UniqueKey
 
 /**
  * @author Graeme Rocher
@@ -34,4 +37,32 @@ class NaturalId {
      * Whether the natural id is mutable
      */
     boolean mutable = false
+
+    /**
+     * Creates the unique key for the natural identifier
+     * @param persistentClass The persistent class
+     * @return An Optional containing the UniqueKey if properties were found, otherwise empty
+     */
+    Optional<UniqueKey> createUniqueKey(PersistentClass persistentClass) {
+        if (propertyNames == null || propertyNames.isEmpty()) {
+            return Optional.empty()
+        }
+
+        UniqueKey uk = new UniqueKey(persistentClass.table)
+        int pks = 0
+        for (String propertyName in propertyNames) {
+            if (persistentClass.hasProperty(propertyName)) {
+                def property = persistentClass.getProperty(propertyName)
+                property.setNaturalIdentifier(true)
+                property.setUpdatable(mutable)
+                uk.addColumns(property.value)
+                pks++
+            }
+        }
+
+        if (pks > 0) {
+            return Optional.of(uk)
+        }
+        return Optional.empty()
+    }
 }
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java
index b27f4c1e8d..e932f253e8 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java
@@ -28,29 +28,10 @@ public class NaturalIdentifierBinder {
     public void bindNaturalIdentifier(Mapping mapping, PersistentClass persistentClass) {
         Optional.ofNullable(mapping.getIdentity())
                 .map(HibernateIdentity::getNatural)
-                .ifPresent(naturalId -> {
-                    if(CollectionUtils.isEmpty(naturalId.getPropertyNames())) {
-                        return;
-                    }
-                    var uk = new UniqueKey();
-                    uk.setTable(persistentClass.getTable());
-                    Stream<String> stringStream = naturalId.getPropertyNames()
-                            .stream()
-                            .filter(persistentClass::hasProperty);
-                    List<String> list = stringStream.toList();
-                    Integer pks = list.stream()
-                            .map(persistentClass::getProperty)
-                            .map(property -> {
-                                property.setNaturalIdentifier(true);
-                                property.setUpdatable(naturalId.isMutable());
-                                uk.addColumns(property.getValue());
-                                return 1;
-                            })
-                            .reduce(0, Integer::sum);
-                    if (pks > 0) {
-                        uniqueNameGenerator.setGeneratedUniqueName(uk);
-                        persistentClass.getTable().addUniqueKey(uk);
-                    }
-        });
+                .flatMap(naturalId -> naturalId.createUniqueKey(persistentClass))
+                .ifPresent(uk -> {
+                    uniqueNameGenerator.setGeneratedUniqueName(uk);
+                    persistentClass.getTable().addUniqueKey(uk);
+                });
     }
 }
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/NaturalIdSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/NaturalIdSpec.groovy
new file mode 100644
index 0000000000..12e0883a51
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/NaturalIdSpec.groovy
@@ -0,0 +1,93 @@
+package org.grails.orm.hibernate.cfg
+
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator
+import org.hibernate.mapping.Column
+import org.hibernate.mapping.Property
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.Table
+import org.hibernate.mapping.UniqueKey
+import org.hibernate.mapping.Value
+
+class NaturalIdSpec extends HibernateGormDatastoreSpec {
+
+    void "test createUniqueKey with a single property"() {
+        given:
+        def naturalId = new NaturalId(propertyNames: ["id1"], mutable: true)
+        def property = new Property()
+        property.name = "id1"
+        def value = Mock(Value)
+        property.value = value
+        def column = new Column("id1")
+        def table = new Table("test_table")
+        def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        rootClass.addProperty(property)
+        rootClass.table = table
+        value.getSelectables() >> [column]
+        value.hasAnyUpdatableColumns() >> true
+
+        when:
+        def result = naturalId.createUniqueKey(rootClass)
+
+        then:
+        result.isPresent()
+        def uk = result.get()
+        uk.table == table
+        uk.columnSpan == 1
+        property.isNaturalIdentifier()
+        property.isUpdateable()
+    }
+
+    void "test createUniqueKey with composite property"() {
+        given:
+        def naturalId = new NaturalId(propertyNames: ["id1", "id2"], mutable: false)
+        def property1 = new Property()
+        property1.name = "id1"
+        def value1 = Mock(Value)
+        property1.value = value1
+        def column1 = new Column("id1")
+        
+        def property2 = new Property()
+        property2.name = "id2"
+        def value2 = Mock(Value)
+        property2.value = value2
+        def column2 = new Column("id2")
+        
+        def table = new Table("test_table")
+        def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        rootClass.addProperty(property1)
+        rootClass.addProperty(property2)
+        rootClass.table = table
+        value1.getSelectables() >> [column1]
+        value1.hasAnyUpdatableColumns() >> false
+        value2.getSelectables() >> [column2]
+        value2.hasAnyUpdatableColumns() >> false
+
+        when:
+        def result = naturalId.createUniqueKey(rootClass)
+
+        then:
+        result.isPresent()
+        def uk = result.get()
+        uk.table == table
+        uk.columnSpan == 2
+        property1.isNaturalIdentifier()
+        !property1.isUpdateable()
+        property2.isNaturalIdentifier()
+        !property2.isUpdateable()
+    }
+
+    void "test createUniqueKey with empty property names"() {
+        given:
+        def naturalId = new NaturalId(propertyNames: [], mutable: false)
+        def table = new Table("test_table")
+        def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        rootClass.table = table
+
+        when:
+        def result = naturalId.createUniqueKey(rootClass)
+
+        then:
+        result.isEmpty()
+    }
+}
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy
index 6abb9c8a30..103ecb81b6 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy
@@ -2,150 +2,53 @@ package org.grails.orm.hibernate.cfg.domainbinding
 
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.CompositeIdentity
-import org.grails.orm.hibernate.cfg.Identity
 import org.grails.orm.hibernate.cfg.Mapping
 import org.grails.orm.hibernate.cfg.NaturalId
-
-import org.hibernate.mapping.Column
-import org.hibernate.mapping.Property
+import org.grails.orm.hibernate.cfg.domainbinding.binder.NaturalIdentifierBinder
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateIdentity
+import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.Table
 import org.hibernate.mapping.UniqueKey
-import org.hibernate.mapping.Value
-import spock.lang.Unroll
-
-import org.grails.orm.hibernate.cfg.domainbinding.binder.NaturalIdentifierBinder
-import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator
 
 class NaturalIdentifierBinderSpec extends HibernateGormDatastoreSpec {
 
-    @Unroll("test bindNaturalIdentifier with a single property and mutable=#isMutable")
-    void "test bindNaturalIdentifier with a single property"(boolean isMutable) {
+    void "test bindNaturalIdentifier calls NaturalId.createUniqueKey and handles result"() {
         given:
-        def mapping = Mock(Mapping.class)
-        def identity = Mock(Identity)
+        def mapping = Mock(Mapping)
+        def identity = Mock(HibernateIdentity)
         def naturalId = Mock(NaturalId)
-        def property = new Property()
-        property.setName("id1")
-        def value = Mock(Value)
-        property.setValue(value)
-        Table table = Mock(Table)
-        def id1 = "id1"
-        mapping.getIdentity() >> identity
-        identity.getNatural() >> naturalId
-        naturalId.getPropertyNames() >> [id1]
-        naturalId.isMutable() >> isMutable
+        def uk = Mock(UniqueKey)
+        def table = Mock(Table)
         def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        rootClass.setIdentifierProperty(property)
         rootClass.setTable(table)
-        value.getSelectables() >> []
-        value.hasAnyUpdatableColumns() >> isMutable
         def uniqueNameGenerator = Mock(UniqueNameGenerator)
         def binder = new NaturalIdentifierBinder(uniqueNameGenerator)
 
-        when:
-        binder.bindNaturalIdentifier(mapping, rootClass)
-
-        then:
-        1 * uniqueNameGenerator.setGeneratedUniqueName(_)
-        property.isNaturalIdentifier()
-        property.isUpdatable() == isMutable
-        1 * table.addUniqueKey(_)
-
-        where:
-        isMutable << [true, false]
-    }
-
-    void "test bindNaturalIdentifier with a composite (multi-property) natural id"() {
-        given:
-        def mapping = Mock(Mapping.class)
-        def identity = Mock(Identity)
-        def naturalId = Mock(NaturalId)
-        def property1 =new Property()
-        property1.setName("id1")
-        def value1 = Mock(Value)
-        property1.setValue(value1)
-        def column1 = new Column('id1')
-        def property2 = new Property()
-        property2.setName("id2")
-        def value2 = Mock(Value)
-        property2.setValue(value2)
-        def column2 = new Column('id2')
-        Table table = Mock(Table)
-
         mapping.getIdentity() >> identity
         identity.getNatural() >> naturalId
-        naturalId.getPropertyNames() >> ["id1", "id2"]
-        naturalId.isMutable() >> true
-
-        def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        rootClass.setTable(table)
-        rootClass.addProperty(property1)
-        rootClass.addProperty(property2)
-        rootClass.getTable() >> table
-
-        value1.getSelectables() >> [column1]
-        value2.getSelectables() >> [column2]
-
-        def uniqueNameGenerator = Mock(UniqueNameGenerator)
-        def binder = new NaturalIdentifierBinder(uniqueNameGenerator)
+        naturalId.createUniqueKey(rootClass) >> Optional.of(uk)
 
         when:
         binder.bindNaturalIdentifier(mapping, rootClass)
 
         then:
-        property1.isNaturalIdentifier()
-        property2.isNaturalIdentifier()
-        1 * table.addUniqueKey(_) >> { uks ->
-            def uk = uks.get(0) as UniqueKey
-            assert uk.getColumnSpan() == 2
-            assert uk.getColumns().get(0) == column1
-            assert  uk.getColumns().get(1)  == column2
-        }
+        1 * uniqueNameGenerator.setGeneratedUniqueName(uk)
+        1 * table.addUniqueKey(uk)
     }
 
-    void "test bindNaturalIdentifier with CompositeIdentity"() {
+    void "test bindNaturalIdentifier when NaturalId returns empty result"() {
         given:
-        def mapping = Mock(Mapping.class)
-        def identity = Mock(CompositeIdentity)
+        def mapping = Mock(Mapping)
+        def identity = Mock(HibernateIdentity)
         def naturalId = Mock(NaturalId)
-        def property = new Property()
-        property.setName("id1")
-        def value = Mock(Value)
-        property.setValue(value)
-        Table table = Mock(Table)
-        def id1 = "id1"
-        mapping.getIdentity() >> identity
-        identity.getNatural() >> naturalId
-        naturalId.getPropertyNames() >> [id1]
-        naturalId.isMutable() >> false
-        def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        rootClass.addProperty(property)
-        rootClass.setTable(table)
-        value.getSelectables() >> []
-
-        def uniqueNameGenerator = Mock(UniqueNameGenerator)
-        def binder = new NaturalIdentifierBinder(uniqueNameGenerator)
-
-        when:
-        binder.bindNaturalIdentifier(mapping, rootClass)
-
-        then:
-        1 * uniqueNameGenerator.setGeneratedUniqueName(_)
-        property.isNaturalIdentifier()
-        1 * table.addUniqueKey(_)
-    }
-
-    void "test bindNaturalIdentifier when no natural id is defined"() {
-        given:
-        def mapping = Mock(Mapping.class)
-        def identity = Mock(Identity)
         def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
         def uniqueNameGenerator = Mock(UniqueNameGenerator)
         def binder = new NaturalIdentifierBinder(uniqueNameGenerator)
 
         mapping.getIdentity() >> identity
-        identity.getNatural() >> null
+        identity.getNatural() >> naturalId
+        naturalId.createUniqueKey(rootClass) >> Optional.empty()
 
         when:
         binder.bindNaturalIdentifier(mapping, rootClass)
@@ -154,26 +57,19 @@ class NaturalIdentifierBinderSpec extends HibernateGormDatastoreSpec {
         0 * uniqueNameGenerator._
     }
 
-    void "test bindNaturalIdentifier when property not found"() {
+    void "test bindNaturalIdentifier when no identity is defined"() {
         given:
-        def mapping = Mock(Mapping.class)
-        def identity = Mock(Identity)
-        def naturalId = Mock(NaturalId)
+        def mapping = Mock(Mapping)
         def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
         def uniqueNameGenerator = Mock(UniqueNameGenerator)
         def binder = new NaturalIdentifierBinder(uniqueNameGenerator)
-        Table table = Mock(Table)
-        mapping.getIdentity() >> identity
-        identity.getNatural() >> naturalId
-        naturalId.getPropertyNames() >> ["nonExistentProperty"]
-        rootClass.setTable(table)
+
+        mapping.getIdentity() >> null
 
         when:
         binder.bindNaturalIdentifier(mapping, rootClass)
 
         then:
-        0 * uniqueNameGenerator.setGeneratedUniqueName(_)
-        0 * table.addUniqueKey(_)
+        0 * uniqueNameGenerator._
     }
-
-}
\ No newline at end of file
+}


(grails-core) 12/16: Refactor bindRoot to use entity.isTablePerHierarchy() and cleanup polymorphic logic

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

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

commit 875d23b941811c06c326d47d2e12fdab44319f51
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 13:15:22 2026 -0600

    Refactor bindRoot to use entity.isTablePerHierarchy() and cleanup polymorphic logic
    
    - Add isTablePerHierarchy() default method to GrailsHibernatePersistentEntity.
    - Update GrailsDomainBinder.bindRoot to use entity.isTablePerHierarchy() and remove redundant tablePerSubclass variable.
    - Remove deprecated setPolymorphic calls and redundant Table argument in bindDiscriminatorProperty.
---
 .../org/grails/orm/hibernate/cfg/GrailsDomainBinder.java      | 11 ++++-------
 .../orm/hibernate/cfg/GrailsHibernatePersistentEntity.java    |  5 +++++
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 8ee1eb3815..e10a69e3a8 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -279,11 +279,8 @@ public class GrailsDomainBinder
         RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator);
         Mapping m = entity.getMappedForm();
         final Mapping finalMapping = m;
-        boolean tablePerSubclass = !m.getTablePerHierarchy();
-        if (!children.isEmpty() && !tablePerSubclass) {
-            // if the root class has children create a discriminator property
-
-            bindDiscriminatorProperty(root.getTable(), root, m);
+        if (!children.isEmpty() && entity.isTablePerHierarchy()) {
+            bindDiscriminatorProperty(root, m);
         }
         // bind the sub classes
         children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
@@ -374,11 +371,11 @@ public class GrailsDomainBinder
      * Creates and binds the discriminator property used in table-per-hierarchy inheritance to
      * discriminate between sub class instances
      *
-     * @param table       The table to bind onto
      * @param entity      The root class entity
      * @param someMapping The mappings instance
      */
-    private void bindDiscriminatorProperty(Table table, RootClass entity, Mapping someMapping) {
+    private void bindDiscriminatorProperty(RootClass entity, Mapping someMapping) {
+        Table table = entity.getTable();
         SimpleValue d = new BasicValue(metadataBuildingContext, table);
         entity.setDiscriminator(d);
         DiscriminatorConfig discriminatorConfig = someMapping.getDiscriminator();
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java
index f055ebc521..d27f5aa8c0 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java
@@ -36,6 +36,11 @@ package org.grails.orm.hibernate.cfg;
          return getHibernateRootEntity().getMappedForm();
      }
  
+     default boolean isTablePerHierarchy() {
+         Mapping mapping = getMappedForm();
+         return mapping == null || mapping.getTablePerHierarchy();
+     }
+
      default boolean isTablePerHierarchySubclass() {
          Mapping rootMapping = getRootMapping();
          return !this.isRoot() && (rootMapping == null || rootMapping.getTablePerHierarchy());


(grails-core) 08/16: Refactor joined subclass binding to a dedicated binder class

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

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

commit c034b6c49c768f564112a785d9f0955a70c8a931
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 11:29:30 2026 -0600

    Refactor joined subclass binding to a dedicated binder class
    
    - Create JoinedSubClassBinder to handle table-per-subclass mapping.
    - Update GrailsDomainBinder to use JoinedSubClassBinder as a local dependency.
    - Add JoinedSubClassBinderSpec using real entity classes for comprehensive testing.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 91 +++++----------------
 .../domainbinding/binder/JoinedSubClassBinder.java | 93 ++++++++++++++++++++++
 .../util/MultiTenantFilterBinder.java              |  2 -
 .../binder/JoinedSubClassBinderSpec.groovy         | 86 ++++++++++++++++++++
 4 files changed, 201 insertions(+), 71 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 6392fd1a42..b53197732b 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -31,6 +31,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyProvider;
 import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.IdentityBinder;
+import org.grails.orm.hibernate.cfg.domainbinding.binder.JoinedSubClassBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder;
@@ -228,12 +229,13 @@ public class GrailsDomainBinder
         IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder);
         VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new);
         MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder();
+        JoinedSubClassBinder joinedSubClassBinder = new JoinedSubClassBinder(metadataBuildingContext, namingStrategy, new SimpleValueColumnBinder(), columnNameForPropertyAndPathFetcher, classBinder);
 
         hibernateMappingContext
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder));
     }
 
 
@@ -264,7 +266,7 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
@@ -281,7 +283,7 @@ public class GrailsDomainBinder
                 bindDiscriminatorProperty(root.getTable(), root, m);
             }
             // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder ));
+            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder));
         }
 
         multiTenantFilterBinder.addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
@@ -311,9 +313,9 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder) {
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder) {
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder);
 
 
         parent.addSubclass(subClass);
@@ -324,25 +326,28 @@ public class GrailsDomainBinder
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
-            subClass = new JoinedSubclass(parent, this.metadataBuildingContext);
-            bindJoinedSubClass(subEntity, (JoinedSubclass) subClass, mappings, sessionFactoryBeanName, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator);
+            var joined = new JoinedSubclass(parent, this.metadataBuildingContext);
+            joinedSubClassBinder.bindJoinedSubClass(subEntity, joined, mappings);
+            subClass = joined;
         }
         else if(m.isTablePerConcreteClass()) {
-            subClass = new UnionSubclass(parent, this.metadataBuildingContext);
-            bindUnionSubclass(subEntity, (UnionSubclass) subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder, classBinder, propertyFromValueCreator);
+            var union  = new UnionSubclass(parent, this.metadataBuildingContext);
+            bindUnionSubclass(subEntity,  union, mappings, sessionFactoryBeanName, grailsPropertyBinder, classBinder, propertyFromValueCreator);
+            subClass = union;
         }
         else {
-            subClass = new SingleTableSubclass(parent, this.metadataBuildingContext);
-            subClass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
-            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder);
+            var singleTableSubclass = new SingleTableSubclass(parent, this.metadataBuildingContext);
+            singleTableSubclass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
+            bindSubClass(subEntity, singleTableSubclass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder);
+            subClass = singleTableSubclass;
         }
         subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1));
         subClass.setDynamicUpdate(m.getDynamicUpdate());
@@ -351,6 +356,7 @@ public class GrailsDomainBinder
         subClass.setAbstract(subEntity.isAbstract());
         subClass.setEntityName(subEntity.getName());
         subClass.setJpaEntityName(GrailsHibernateUtil.unqualify(subEntity.getName()));
+        createClassProperties(subEntity, subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
         return subClass;
     }
 
@@ -379,60 +385,8 @@ public class GrailsDomainBinder
                         " -> " + unionSubclass.getTable().getName()
         );
 
-        createClassProperties(subClass, unionSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
 
     }
-    /**
-     * Binds a joined sub-class mapping using table-per-subclass
-     *
-     * @param sub                    The Grails sub class
-     * @param joinedSubclass         The Hibernate Subclass object
-     * @param mappings               The mappings Object
-     * @param sessionFactoryBeanName the session factory bean name
-     */
-    private void bindJoinedSubClass(GrailsHibernatePersistentEntity sub, JoinedSubclass joinedSubclass,
-                                    InFlightMetadataCollector mappings, String sessionFactoryBeanName, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) {
-        classBinder.bindClass(sub, joinedSubclass, mappings);
-
-        String schemaName = sub.getSchema(mappings);
-        String catalogName = sub.getCatalog(mappings);
-
-        Table mytable = mappings.addTable(
-                schemaName, catalogName,
-                getJoinedSubClassTableName(sub, joinedSubclass, null, mappings),
-                null, false, metadataBuildingContext);
-
-        joinedSubclass.setTable(mytable);
-        LOG.info("Mapping joined-subclass: " + joinedSubclass.getEntityName() +
-                " -> " + joinedSubclass.getTable().getName());
-
-        SimpleValue key = new DependantValue(metadataBuildingContext, mytable, joinedSubclass.getIdentifier());
-        joinedSubclass.setKey(key);
-        var identifier = sub.getIdentity();
-        String columnName = columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(identifier, EMPTY_PATH, null);
-        new SimpleValueColumnBinder().bindSimpleValue(key, identifier.getType().getName(), columnName, false);
-
-        joinedSubclass.createPrimaryKey();
-        joinedSubclass.createForeignKey();
-
-        // properties
-        createClassProperties(sub, joinedSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
-    }
-
-    private String getJoinedSubClassTableName(
-            GrailsHibernatePersistentEntity sub, PersistentClass model, Table denormalizedSuperTable,
-            InFlightMetadataCollector mappings) {
-
-        String logicalTableName = GrailsHibernateUtil.unqualify(model.getEntityName());
-        String physicalTableName = new TableNameFetcher(getNamingStrategy()).getTableName(sub);
-
-        String schemaName = sub.getSchema(mappings);
-        String catalogName = sub.getCatalog(mappings);
-
-        mappings.addTableNameBinding(schemaName, catalogName, logicalTableName, physicalTableName, denormalizedSuperTable);
-        return physicalTableName;
-    }
-
     /**
      * Binds a sub-class using table-per-hierarchy inheritance mapping
      *
@@ -440,8 +394,8 @@ public class GrailsDomainBinder
      * @param subClass The Hibernate SubClass instance
      * @param mappings The mappings instance
      */
-    private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, Subclass subClass, @Nonnull InFlightMetadataCollector mappings,
-                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder) {
+    private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, SingleTableSubclass subClass, @Nonnull InFlightMetadataCollector mappings,
+                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder) {
         classBinder.bindClass(sub, subClass, mappings);
 
         if (LOG.isDebugEnabled())
@@ -449,7 +403,6 @@ public class GrailsDomainBinder
                     " -> " + subClass.getTable().getName());
 
         // properties
-        createClassProperties(sub, subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder, propertyFromValueCreator);
     }
 
     /**
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinder.java
new file mode 100644
index 0000000000..b4b78c44fd
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinder.java
@@ -0,0 +1,93 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder;
+
+import jakarta.annotation.Nonnull;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
+import org.grails.orm.hibernate.cfg.GrailsHibernateUtil;
+import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy;
+import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher;
+import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator;
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher;
+import org.hibernate.boot.spi.InFlightMetadataCollector;
+import org.hibernate.boot.spi.MetadataBuildingContext;
+import org.hibernate.mapping.DependantValue;
+import org.hibernate.mapping.JoinedSubclass;
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.mapping.SimpleValue;
+import org.hibernate.mapping.Table;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Binds a joined sub-class mapping using table-per-subclass
+ *
+ * @since 7.0
+ */
+public class JoinedSubClassBinder {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JoinedSubClassBinder.class);
+    private static final String EMPTY_PATH = "";
+
+    private final MetadataBuildingContext metadataBuildingContext;
+    private final PersistentEntityNamingStrategy namingStrategy;
+    private final SimpleValueColumnBinder simpleValueColumnBinder;
+    private final ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher;
+    private final ClassBinder classBinder;
+
+    public JoinedSubClassBinder(MetadataBuildingContext metadataBuildingContext, PersistentEntityNamingStrategy namingStrategy, SimpleValueColumnBinder simpleValueColumnBinder, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, ClassBinder classBinder) {
+        this.metadataBuildingContext = metadataBuildingContext;
+        this.namingStrategy = namingStrategy;
+        this.simpleValueColumnBinder = simpleValueColumnBinder;
+        this.columnNameForPropertyAndPathFetcher = columnNameForPropertyAndPathFetcher;
+        this.classBinder = classBinder;
+    }
+
+    /**
+     * Binds a joined sub-class mapping using table-per-subclass
+     *
+     * @param sub                    The Grails sub class
+     * @param joinedSubclass         The Hibernate Subclass object
+     * @param mappings               The mappings Object
+     */
+    public void bindJoinedSubClass(GrailsHibernatePersistentEntity sub,
+                                   JoinedSubclass joinedSubclass,
+                                    InFlightMetadataCollector mappings) {
+        classBinder.bindClass(sub, joinedSubclass, mappings);
+
+        String schemaName = sub.getSchema(mappings);
+        String catalogName = sub.getCatalog(mappings);
+
+        Table mytable = mappings.addTable(
+                schemaName, catalogName,
+                getJoinedSubClassTableName(sub, joinedSubclass, null, mappings),
+                null, false, metadataBuildingContext);
+
+        joinedSubclass.setTable(mytable);
+        if (LOG.isInfoEnabled()) {
+            LOG.info("Mapping joined-subclass: " + joinedSubclass.getEntityName() +
+                    " -> " + joinedSubclass.getTable().getName());
+        }
+
+        SimpleValue key = new DependantValue(metadataBuildingContext, mytable, joinedSubclass.getIdentifier());
+        joinedSubclass.setKey(key);
+        var identifier = sub.getIdentity();
+        String columnName = columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(identifier, EMPTY_PATH, null);
+        simpleValueColumnBinder.bindSimpleValue(key, identifier.getType().getName(), columnName, false);
+
+        joinedSubclass.createPrimaryKey();
+        joinedSubclass.createForeignKey();
+    }
+
+    private String getJoinedSubClassTableName(
+            GrailsHibernatePersistentEntity sub, PersistentClass model, Table denormalizedSuperTable,
+            InFlightMetadataCollector mappings) {
+
+        String logicalTableName = GrailsHibernateUtil.unqualify(model.getEntityName());
+        String physicalTableName = new TableNameFetcher(namingStrategy).getTableName(sub);
+
+        String schemaName = sub.getSchema(mappings);
+        String catalogName = sub.getCatalog(mappings);
+
+        mappings.addTableNameBinding(schemaName, catalogName, logicalTableName, physicalTableName, denormalizedSuperTable);
+        return physicalTableName;
+    }
+}
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java
index a74d51f0bb..ac8315fa54 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java
@@ -22,8 +22,6 @@ import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
 /**
  * Utility class for binding multi-tenant filters to the Hibernate meta model.
  *
- * @author Walter Duque de Estrada
- * @author Graeme Rocher
  * @since 7.0
  */
 public class MultiTenantFilterBinder {
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinderSpec.groovy
new file mode 100644
index 0000000000..5f82336329
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/JoinedSubClassBinderSpec.groovy
@@ -0,0 +1,86 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder
+
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
+import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.hibernate.mapping.JoinedSubclass
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.Table
+import org.hibernate.mapping.SimpleValue
+import org.grails.datastore.mapping.model.types.Identity
+
+/**
+ * Tests for JoinedSubClassBinder using real entity classes.
+ */
+class JoinedSubClassBinderSpec extends HibernateGormDatastoreSpec {
+
+    JoinedSubClassBinder binder
+    ColumnNameForPropertyAndPathFetcher fetcher
+    ClassBinder classBinder = new ClassBinder()
+    SimpleValueColumnBinder simpleValueColumnBinder = new SimpleValueColumnBinder()
+
+    void setup() {
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def namingStrategy = getGrailsDomainBinder().getNamingStrategy()
+        def backticksRemover = new org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover()
+        def defaultColumnNameFetcher = new org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher(namingStrategy, backticksRemover)
+        
+        fetcher = new org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        binder = new JoinedSubClassBinder(buildingContext, namingStrategy, simpleValueColumnBinder, fetcher, classBinder)
+    }
+
+    void "test bind joined subclass with real entities"() {
+        given:
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def mappings = buildingContext.getMetadataCollector()
+        
+        // Register entities in mapping context
+        def rootEntity = createPersistentEntity(JoinedSubClassRoot)
+        def subEntity = createPersistentEntity(JoinedSubClassSub)
+        
+        // Setup Hibernate RootClass
+        def rootClass = new RootClass(buildingContext)
+        rootClass.setEntityName(JoinedSubClassRoot.name)
+        def rootTable = new Table("JS_ROOT_TABLE")
+        rootTable.setName("JS_ROOT_TABLE")
+        rootClass.setTable(rootTable)
+        
+        def idProperty = new org.hibernate.mapping.Property()
+        idProperty.setName("id")
+        def idValue = new org.hibernate.mapping.BasicValue(buildingContext, rootTable)
+        idValue.setTypeName("long")
+        idProperty.setValue(idValue)
+        rootClass.setIdentifier(idValue)
+        rootClass.setIdentifierProperty(idProperty)
+        rootClass.createPrimaryKey()
+        
+        // The JoinedSubclass needs the parent PersistentClass
+        def joinedSubclass = new JoinedSubclass(rootClass, buildingContext)
+        joinedSubclass.setEntityName(JoinedSubClassSub.name)
+
+        when:
+        binder.bindJoinedSubClass(subEntity, joinedSubclass, mappings)
+
+        then:
+        joinedSubclass.getTable() != null
+        joinedSubclass.getTable().getName() != "JS_ROOT_TABLE"
+        joinedSubclass.getKey() != null
+        joinedSubclass.getKey().getColumnSpan() > 0
+        joinedSubclass.getTable().getPrimaryKey() != null
+    }
+}
+
+@Entity
+class JoinedSubClassRoot {
+    Long id
+}
+
+@Entity
+class JoinedSubClassSub extends JoinedSubClassRoot {
+    String name
+    static mapping = {
+        tablePerHierarchy false
+    }
+}


(grails-core) 03/16: Refactor binders in GrailsDomainBinder to be local variables

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

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

commit c2e74db3971907a9eff44e173e0ac5d08e4f1f97
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Feb 14 14:21:05 2026 -0600

    Refactor binders in GrailsDomainBinder to be local variables
    
    - Convert collectionBinder, componentPropertyBinder, grailsPropertyBinder, compositeIdBinder, identityBinder, and versionBinder to local variables in the contribute method.
    - Update binding method signatures to accept these binders as parameters.
    - Update unit tests to instantiate these binders via helper methods and pass them to bindRoot.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      |  82 +++++---------
 .../cfg/domainbinding/CollectionBinderSpec.groovy  | 111 ++++++++++++++++++-
 .../CollectionSecondPassBinderSpec.groovy          | 112 ++++++++++++++++++-
 .../domainbinding/GrailsPropertyBinderSpec.groovy  | 123 +++++++++++++++++++--
 .../domainbinding/ListSecondPassBinderSpec.groovy  | 112 ++++++++++++++++++-
 .../domainbinding/MapSecondPassBinderSpec.groovy   | 112 ++++++++++++++++++-
 6 files changed, 581 insertions(+), 71 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 89ac71987d..9e37b89f25 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -134,12 +134,6 @@ public class GrailsDomainBinder
     private MetadataBuildingContext metadataBuildingContext;
     private MappingCacheHolder mappingCacheHolder;
     private CollectionHolder collectionHolder;
-    private GrailsPropertyBinder grailsPropertyBinder;
-    private CollectionBinder collectionBinder;
-    private CompositeIdBinder compositeIdBinder;
-    private IdentityBinder identityBinder;
-    private VersionBinder versionBinder;
-    private ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher;
 
 
     public JdbcEnvironment getJdbcEnvironment() {
@@ -197,7 +191,7 @@ public class GrailsDomainBinder
         PersistentEntityNamingStrategy namingStrategy = getNamingStrategy();
         JdbcEnvironment jdbcEnvironment = getJdbcEnvironment();
         DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover);
-        this.columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover);
+        ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover);
         SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment);
         EnumTypeBinder enumTypeBinderToUse = enumTypeBinder != null ? enumTypeBinder : new EnumTypeBinder();
         SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher();
@@ -212,7 +206,7 @@ public class GrailsDomainBinder
         OneToOneBinder oneToOneBinder = new OneToOneBinder(namingStrategy, simpleValueBinder);
         ManyToOneBinder manyToOneBinder = new ManyToOneBinder(namingStrategy, simpleValueBinder, new ManyToOneValuesBinder(), compositeIdentifierToManyToOneBinder, simpleValueColumnFetcher);
 
-        this.collectionBinder = new CollectionBinder(
+        CollectionBinder collectionBinder = new CollectionBinder(
                 metadataBuildingContext,
                 this,
                 namingStrategy,
@@ -223,7 +217,7 @@ public class GrailsDomainBinder
                 compositeIdentifierToManyToOneBinder,
                 simpleValueColumnFetcher
         );
-        this.componentPropertyBinder = new ComponentPropertyBinder(
+        ComponentPropertyBinder componentPropertyBinder = new ComponentPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
                 jdbcEnvironment,
@@ -238,7 +232,7 @@ public class GrailsDomainBinder
                 manyToOneBinder,
                 columnNameForPropertyAndPathFetcher
         );
-        this.grailsPropertyBinder = new GrailsPropertyBinder(
+        GrailsPropertyBinder grailsPropertyBinder = new GrailsPropertyBinder(
                 metadataBuildingContext,
                 namingStrategy,
                 getCollectionHolder(),
@@ -251,17 +245,17 @@ public class GrailsDomainBinder
                 manyToOneBinder,
                 propertyFromValueCreator
         );
-        this.compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder);
+        CompositeIdBinder compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder);
         PropertyBinder propertyBinder = new PropertyBinder();
         SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinder);
-        this.identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder);
-        this.versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new);
+        IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder);
+        VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new);
 
         hibernateMappingContext
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder));
     }
 
 
@@ -292,13 +286,13 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher) {
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder) {
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
         }
         var children = entity.getChildEntities(dataSourceName);
-        RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName);
+        RootClass root = bindRootPersistentClassCommonValues(entity, children, mappings, sessionFactoryBeanName, identityBinder, versionBinder, grailsPropertyBinder);
         if (root.isPolymorphic()) {
             Mapping m = entity.getMappedForm();
             final Mapping finalMapping = m;
@@ -309,7 +303,7 @@ public class GrailsDomainBinder
                 bindDiscriminatorProperty(root.getTable(), root, m);
             }
             // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher ));
+            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder ));
         }
 
         addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
@@ -380,9 +374,9 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher) {
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder) {
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder);
 
 
         parent.addSubclass(subClass);
@@ -393,25 +387,25 @@ public class GrailsDomainBinder
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher) {
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder) {
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
             subClass = new JoinedSubclass(parent, this.metadataBuildingContext);
-            bindJoinedSubClass(subEntity, (JoinedSubclass) subClass, mappings, sessionFactoryBeanName);
+            bindJoinedSubClass(subEntity, (JoinedSubclass) subClass, mappings, sessionFactoryBeanName, columnNameForPropertyAndPathFetcher, grailsPropertyBinder);
         }
         else if(m.isTablePerConcreteClass()) {
             subClass = new UnionSubclass(parent, this.metadataBuildingContext);
-            bindUnionSubclass(subEntity, (UnionSubclass) subClass, mappings, sessionFactoryBeanName);
+            bindUnionSubclass(subEntity, (UnionSubclass) subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
         }
         else {
             subClass = new SingleTableSubclass(parent, this.metadataBuildingContext);
             subClass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
-            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher);
+            bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder);
         }
         subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1));
         subClass.setDynamicUpdate(m.getDynamicUpdate());
@@ -425,7 +419,7 @@ public class GrailsDomainBinder
 
 
     private void bindUnionSubclass(@Nonnull GrailsHibernatePersistentEntity subClass, UnionSubclass unionSubclass,
-                                  @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName) throws MappingException {
+                                  @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder) throws MappingException {
         classBinding.bindClass(subClass, unionSubclass, mappings);
 
         String schema = subClass.getSchema(mappings);
@@ -448,7 +442,7 @@ public class GrailsDomainBinder
                         " -> " + unionSubclass.getTable().getName()
         );
 
-        createClassProperties(subClass, unionSubclass, mappings, sessionFactoryBeanName);
+        createClassProperties(subClass, unionSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
 
     }
     /**
@@ -460,7 +454,7 @@ public class GrailsDomainBinder
      * @param sessionFactoryBeanName the session factory bean name
      */
     private void bindJoinedSubClass(GrailsHibernatePersistentEntity sub, JoinedSubclass joinedSubclass,
-                                    InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+                                    InFlightMetadataCollector mappings, String sessionFactoryBeanName, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder) {
         classBinding.bindClass(sub, joinedSubclass, mappings);
 
         String schemaName = sub.getSchema(mappings);
@@ -485,7 +479,7 @@ public class GrailsDomainBinder
         joinedSubclass.createForeignKey();
 
         // properties
-        createClassProperties(sub, joinedSubclass, mappings, sessionFactoryBeanName);
+        createClassProperties(sub, joinedSubclass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
     }
 
     private String getJoinedSubClassTableName(
@@ -510,7 +504,7 @@ public class GrailsDomainBinder
      * @param mappings The mappings instance
      */
     private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, Subclass subClass, @Nonnull InFlightMetadataCollector mappings,
-                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher) {
+                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder) {
         classBinding.bindClass(sub, subClass, mappings);
 
         if (LOG.isDebugEnabled())
@@ -518,7 +512,7 @@ public class GrailsDomainBinder
                     " -> " + subClass.getTable().getName());
 
         // properties
-        createClassProperties(sub, subClass, mappings, sessionFactoryBeanName);
+        createClassProperties(sub, subClass, mappings, sessionFactoryBeanName, grailsPropertyBinder);
     }
 
     /**
@@ -580,7 +574,10 @@ public class GrailsDomainBinder
     private RootClass bindRootPersistentClassCommonValues(@Nonnull GrailsHibernatePersistentEntity domainClass,
                                                        @Nonnull java.util.Collection<GrailsHibernatePersistentEntity> children,
                                                        @Nonnull InFlightMetadataCollector mappings,
-                                                       String sessionFactoryBeanName
+                                                       String sessionFactoryBeanName,
+                                                       IdentityBinder identityBinder,
+                                                       VersionBinder versionBinder,
+                                                       GrailsPropertyBinder grailsPropertyBinder
     ) {
 
         RootClass root = new RootClass(this.metadataBuildingContext);
@@ -629,7 +626,7 @@ public class GrailsDomainBinder
         identityBinder.bindIdentity(domainClass, root, mappings, gormMapping, sessionFactoryBeanName);
         versionBinder.bindVersion(domainClass.getVersion(), root);
         root.createPrimaryKey();
-        createClassProperties(domainClass, root, mappings, sessionFactoryBeanName);
+        createClassProperties(domainClass, root, mappings, sessionFactoryBeanName, grailsPropertyBinder);
 
         return root;
     }
@@ -645,7 +642,7 @@ public class GrailsDomainBinder
      * @param sessionFactoryBeanName  the session factory bean name
      */
     private void createClassProperties(@Nonnull GrailsHibernatePersistentEntity domainClass, PersistentClass persistentClass,
-                                         @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+                                         @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, GrailsPropertyBinder grailsPropertyBinder) {
 
 
 
@@ -676,25 +673,6 @@ public class GrailsDomainBinder
         return propertyFromValueCreator;
     }
 
-    public GrailsPropertyBinder getGrailsPropertyBinder() {
-        return grailsPropertyBinder;
-    }
-
-    public CollectionBinder getCollectionBinder() {
-        return collectionBinder;
-    }
-
-    public CompositeIdBinder getCompositeIdBinder() {
-        return compositeIdBinder;
-    }
-
-    public IdentityBinder getIdentityBinder() {
-        return identityBinder;
-    }
-
-    public VersionBinder getVersionBinder() {
-        return versionBinder;
-    }
 
     @Override
     public String getContributorName() {
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
index e1dd19fdb6..31c0bef73a 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
@@ -6,9 +6,118 @@ import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty
 import org.hibernate.mapping.OneToMany
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.Set
+import org.hibernate.boot.spi.MetadataBuildingContext
+import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
+import org.grails.orm.hibernate.cfg.GrailsDomainBinder
+
+import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.IdentityBinder
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.grails.orm.hibernate.cfg.domainbinding.binder.VersionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.PropertyBinder
+import org.hibernate.mapping.BasicValue
+import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
 
 class CollectionBinderSpec extends HibernateGormDatastoreSpec {
 
+    protected Map getBinders(GrailsDomainBinder binder) {
+        MetadataBuildingContext metadataBuildingContext = binder.getMetadataBuildingContext()
+        PersistentEntityNamingStrategy namingStrategy = binder.getNamingStrategy()
+        JdbcEnvironment jdbcEnvironment = binder.getJdbcEnvironment()
+        BackticksRemover backticksRemover = new BackticksRemover()
+        DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
+        ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
+        EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
+        SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
+        CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder = new CompositeIdentifierToManyToOneBinder(
+                new org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator(),
+                new TableNameFetcher(namingStrategy),
+                namingStrategy,
+                defaultColumnNameFetcher,
+                backticksRemover,
+                simpleValueBinder
+        )
+        OneToOneBinder oneToOneBinder = new OneToOneBinder(namingStrategy, simpleValueBinder)
+        ManyToOneBinder manyToOneBinder = new ManyToOneBinder(namingStrategy, simpleValueBinder, new ManyToOneValuesBinder(), compositeIdentifierToManyToOneBinder, simpleValueColumnFetcher)
+
+        CollectionBinder collectionBinder = new CollectionBinder(
+                metadataBuildingContext,
+                binder,
+                namingStrategy,
+                jdbcEnvironment,
+                simpleValueBinder,
+                enumTypeBinderToUse,
+                manyToOneBinder,
+                compositeIdentifierToManyToOneBinder,
+                simpleValueColumnFetcher
+        )
+        ComponentPropertyBinder componentPropertyBinder = new ComponentPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                jdbcEnvironment,
+                binder.getMappingCacheHolder(),
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                collectionBinder,
+                new PropertyFromValueCreator(),
+                null,
+                simpleValueBinder,
+                oneToOneBinder,
+                manyToOneBinder,
+                columnNameForPropertyAndPathFetcher
+        )
+        GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                componentPropertyBinder,
+                collectionBinder,
+                simpleValueBinder,
+                columnNameForPropertyAndPathFetcher,
+                oneToOneBinder,
+                manyToOneBinder,
+                new PropertyFromValueCreator()
+        )
+        CompositeIdBinder compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder)
+        PropertyBinder propertyBinderHelper = new PropertyBinder()
+        SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinderHelper)
+        IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder)
+        VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinderHelper, BasicValue::new)
+
+        return [
+            propertyBinder: propertyBinder,
+            collectionBinder: collectionBinder,
+            identityBinder: identityBinder,
+            versionBinder: versionBinder,
+            defaultColumnNameFetcher: defaultColumnNameFetcher,
+            columnNameForPropertyAndPathFetcher: columnNameForPropertyAndPathFetcher
+        ]
+    }
+
+    protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+        def binders = getBinders(binder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+    }
+
     void setupSpec() {
         manager.addAllDomainClasses([
             org.apache.grails.data.testing.tck.domains.Pet,
@@ -20,7 +129,7 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
     void "Test bind collection"() {
         given:
         def binder = getGrailsDomainBinder()
-        def collectionBinder = binder.getCollectionBinder()
+        def collectionBinder = getBinders(binder).collectionBinder
         def collector = getCollector()
 
         def personEntity = getPersistentEntity(org.apache.grails.data.testing.tck.domains.Person) as GrailsHibernatePersistentEntity
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
index 6af22f62d1..cd46b537a6 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
@@ -5,14 +5,121 @@ import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
+import org.hibernate.boot.spi.MetadataBuildingContext
+import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder
+import org.grails.orm.hibernate.cfg.GrailsDomainBinder
 
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPassBinder
 
+import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
+import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.IdentityBinder
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.grails.orm.hibernate.cfg.domainbinding.binder.VersionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.PropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
+import org.hibernate.mapping.BasicValue
+
 class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
+    protected Map getBinders(GrailsDomainBinder binder) {
+        MetadataBuildingContext metadataBuildingContext = binder.getMetadataBuildingContext()
+        PersistentEntityNamingStrategy namingStrategy = binder.getNamingStrategy()
+        JdbcEnvironment jdbcEnvironment = binder.getJdbcEnvironment()
+        BackticksRemover backticksRemover = new BackticksRemover()
+        DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
+        ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
+        EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
+        SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
+        CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder = new CompositeIdentifierToManyToOneBinder(
+                new org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator(),
+                new TableNameFetcher(namingStrategy),
+                namingStrategy,
+                defaultColumnNameFetcher,
+                backticksRemover,
+                simpleValueBinder
+        )
+        OneToOneBinder oneToOneBinder = new OneToOneBinder(namingStrategy, simpleValueBinder)
+        ManyToOneBinder manyToOneBinder = new ManyToOneBinder(namingStrategy, simpleValueBinder, new ManyToOneValuesBinder(), compositeIdentifierToManyToOneBinder, simpleValueColumnFetcher)
+
+        CollectionBinder collectionBinder = new CollectionBinder(
+                metadataBuildingContext,
+                binder,
+                namingStrategy,
+                jdbcEnvironment,
+                simpleValueBinder,
+                enumTypeBinderToUse,
+                manyToOneBinder,
+                compositeIdentifierToManyToOneBinder,
+                simpleValueColumnFetcher
+        )
+        ComponentPropertyBinder componentPropertyBinder = new ComponentPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                jdbcEnvironment,
+                binder.getMappingCacheHolder(),
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                collectionBinder,
+                new PropertyFromValueCreator(),
+                null,
+                simpleValueBinder,
+                oneToOneBinder,
+                manyToOneBinder,
+                columnNameForPropertyAndPathFetcher
+        )
+        GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                componentPropertyBinder,
+                collectionBinder,
+                simpleValueBinder,
+                columnNameForPropertyAndPathFetcher,
+                oneToOneBinder,
+                manyToOneBinder,
+                new PropertyFromValueCreator()
+        )
+        CompositeIdBinder compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder)
+        PropertyBinder propertyBinderHelper = new PropertyBinder()
+        SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinderHelper)
+        IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder)
+        VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinderHelper, BasicValue::new)
+
+        return [
+            propertyBinder: propertyBinder,
+            collectionBinder: collectionBinder,
+            identityBinder: identityBinder,
+            versionBinder: versionBinder,
+            defaultColumnNameFetcher: defaultColumnNameFetcher,
+            columnNameForPropertyAndPathFetcher: columnNameForPropertyAndPathFetcher
+        ]
+    }
+
+    protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+        def binders = getBinders(binder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+    }
+
     void setupSpec() {
         manager.addAllDomainClasses([
                 Author,
@@ -24,7 +131,8 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         given:
         def collector = getCollector()
         def binder = getGrailsDomainBinder()
-        def collectionBinder = binder.getCollectionBinder()
+        def binders = getBinders(binder)
+        def collectionBinder = binders.collectionBinder
         def namingStrategy = binder.getNamingStrategy()
         def jdbcEnvironment = binder.getJdbcEnvironment()
         def collectionSecondPassBinder = new CollectionSecondPassBinder(
@@ -42,7 +150,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = getPersistentEntity(Book) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
+        bindRoot(binder, bookEntity, collector, "sessionFactory")
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
index dba76ebbe5..4449b59ca1 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
@@ -23,8 +23,107 @@ import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndP
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
 
+import org.hibernate.mapping.BasicValue
+import org.hibernate.boot.spi.MetadataBuildingContext
+import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder
+
+import org.grails.orm.hibernate.cfg.domainbinding.binder.IdentityBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.VersionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.PropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
+import org.hibernate.boot.spi.InFlightMetadataCollector
+
 class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
 
+    protected Map getBinders(GrailsDomainBinder binder) {
+        MetadataBuildingContext metadataBuildingContext = binder.getMetadataBuildingContext()
+        PersistentEntityNamingStrategy namingStrategy = binder.getNamingStrategy()
+        JdbcEnvironment jdbcEnvironment = binder.getJdbcEnvironment()
+        BackticksRemover backticksRemover = new BackticksRemover()
+        DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
+        ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
+        EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
+        SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
+        CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder = new CompositeIdentifierToManyToOneBinder(
+                new org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator(),
+                new TableNameFetcher(namingStrategy),
+                namingStrategy,
+                defaultColumnNameFetcher,
+                backticksRemover,
+                simpleValueBinder
+        )
+        OneToOneBinder oneToOneBinder = new OneToOneBinder(namingStrategy, simpleValueBinder)
+        ManyToOneBinder manyToOneBinder = new ManyToOneBinder(namingStrategy, simpleValueBinder, new ManyToOneValuesBinder(), compositeIdentifierToManyToOneBinder, simpleValueColumnFetcher)
+
+        CollectionBinder collectionBinder = new CollectionBinder(
+                metadataBuildingContext,
+                binder,
+                namingStrategy,
+                jdbcEnvironment,
+                simpleValueBinder,
+                enumTypeBinderToUse,
+                manyToOneBinder,
+                compositeIdentifierToManyToOneBinder,
+                simpleValueColumnFetcher
+        )
+        ComponentPropertyBinder componentPropertyBinder = new ComponentPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                jdbcEnvironment,
+                binder.getMappingCacheHolder(),
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                collectionBinder,
+                new PropertyFromValueCreator(),
+                null,
+                simpleValueBinder,
+                oneToOneBinder,
+                manyToOneBinder,
+                columnNameForPropertyAndPathFetcher
+        )
+        GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                componentPropertyBinder,
+                collectionBinder,
+                simpleValueBinder,
+                columnNameForPropertyAndPathFetcher,
+                oneToOneBinder,
+                manyToOneBinder,
+                new PropertyFromValueCreator()
+        )
+        CompositeIdBinder compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder)
+        PropertyBinder propertyBinderHelper = new PropertyBinder()
+        SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinderHelper)
+        IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder)
+        VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinderHelper, BasicValue::new)
+
+        return [
+            propertyBinder: propertyBinder,
+            collectionBinder: collectionBinder,
+            identityBinder: identityBinder,
+            versionBinder: versionBinder,
+            defaultColumnNameFetcher: defaultColumnNameFetcher,
+            columnNameForPropertyAndPathFetcher: columnNameForPropertyAndPathFetcher
+        ]
+    }
+
+    protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+        def binders = getBinders(binder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+    }
+
     void setupSpec() {
         manager.addAllDomainClasses([
             org.apache.grails.data.testing.tck.domains.Pet,
@@ -38,7 +137,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         given:
         def collector = getCollector()
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         def persistentEntity = createPersistentEntity(binder, "SimpleBook", [title: String], [:])
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
         rootClass.setEntityName(persistentEntity.name)
@@ -61,7 +160,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         given:
         def collector = getCollector()
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         def persistentEntity = createPersistentEntity(binder, "EnumBook", [status: java.util.concurrent.TimeUnit], [:])
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
         rootClass.setEntityName(persistentEntity.name)
@@ -83,7 +182,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
     void "Test bind many-to-one"() {
         given:
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         def collector = getCollector()
 
         def petEntity = getPersistentEntity(org.apache.grails.data.testing.tck.domains.Pet) as GrailsHibernatePersistentEntity
@@ -109,7 +208,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         given:
         def collector = getCollector()
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         
         def persistentEntity = createPersistentEntity(binder, "Employee", [name: String, homeAddress: Address], [:], ["homeAddress"])
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
@@ -134,7 +233,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
     void "Test bind set collection"() {
         given:
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         def collector = getCollector()
 
         def personEntity = getPersistentEntity(org.apache.grails.data.testing.tck.domains.Person) as GrailsHibernatePersistentEntity
@@ -161,14 +260,14 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
     void "Test bind list collection"() {
         given:
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         def collector = getCollector()
 
         def bookEntity = createPersistentEntity(ListBook)
         def authorEntity = createPersistentEntity(ListAuthor)
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
+        bindRoot(binder, bookEntity, collector, "sessionFactory")
 
         // Manually create RootClass for the main entity to avoid duplicate property binding
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
@@ -201,14 +300,14 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
     void "Test bind map collection"() {
         given:
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         def collector = getCollector()
 
         def bookEntity = createPersistentEntity(MapBook)
         def authorEntity = createPersistentEntity(MapAuthor)
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
+        bindRoot(binder, bookEntity, collector, "sessionFactory")
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
@@ -245,7 +344,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         def personEntity = getPersistentEntity(org.apache.grails.data.testing.tck.domains.PersonWithCompositeKey) as GrailsHibernatePersistentEntity
         
         when:
-        binder.bindRoot(personEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
+        bindRoot(binder, personEntity, collector, "sessionFactory")
         def rootClass = collector.getEntityBinding(personEntity.name)
 
         then:
@@ -260,7 +359,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
     void "Test bind one-to-one property"() {
         given:
         def binder = getGrailsDomainBinder()
-        def propertyBinder = binder.getGrailsPropertyBinder()
+        def propertyBinder = getBinders(binder).propertyBinder
         def collector = getCollector()
 
         // Create two entities: Author (with hasOne child) and Book (the child)
@@ -268,7 +367,7 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = createPersistentEntity(BookForOneToOne) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
+        bindRoot(binder, bookEntity, collector, "sessionFactory")
 
         // Manually create RootClass for the main entity (AuthorWithOneToOne)
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
index 66d07c71ba..7bf27fe3e4 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
@@ -5,16 +5,123 @@ import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.SimpleValue
+import org.hibernate.boot.spi.MetadataBuildingContext
+import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder
+import org.grails.orm.hibernate.cfg.GrailsDomainBinder
 
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPassBinder
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.ListSecondPassBinder
 
+import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
+import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.IdentityBinder
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.grails.orm.hibernate.cfg.domainbinding.binder.VersionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.PropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
+import org.hibernate.mapping.BasicValue
+
 class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
+    protected Map getBinders(GrailsDomainBinder binder) {
+        MetadataBuildingContext metadataBuildingContext = binder.getMetadataBuildingContext()
+        PersistentEntityNamingStrategy namingStrategy = binder.getNamingStrategy()
+        JdbcEnvironment jdbcEnvironment = binder.getJdbcEnvironment()
+        BackticksRemover backticksRemover = new BackticksRemover()
+        DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
+        ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
+        EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
+        SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
+        CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder = new CompositeIdentifierToManyToOneBinder(
+                new org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator(),
+                new TableNameFetcher(namingStrategy),
+                namingStrategy,
+                defaultColumnNameFetcher,
+                backticksRemover,
+                simpleValueBinder
+        )
+        OneToOneBinder oneToOneBinder = new OneToOneBinder(namingStrategy, simpleValueBinder)
+        ManyToOneBinder manyToOneBinder = new ManyToOneBinder(namingStrategy, simpleValueBinder, new ManyToOneValuesBinder(), compositeIdentifierToManyToOneBinder, simpleValueColumnFetcher)
+
+        CollectionBinder collectionBinder = new CollectionBinder(
+                metadataBuildingContext,
+                binder,
+                namingStrategy,
+                jdbcEnvironment,
+                simpleValueBinder,
+                enumTypeBinderToUse,
+                manyToOneBinder,
+                compositeIdentifierToManyToOneBinder,
+                simpleValueColumnFetcher
+        )
+        ComponentPropertyBinder componentPropertyBinder = new ComponentPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                jdbcEnvironment,
+                binder.getMappingCacheHolder(),
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                collectionBinder,
+                new PropertyFromValueCreator(),
+                null,
+                simpleValueBinder,
+                oneToOneBinder,
+                manyToOneBinder,
+                columnNameForPropertyAndPathFetcher
+        )
+        GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                componentPropertyBinder,
+                collectionBinder,
+                simpleValueBinder,
+                columnNameForPropertyAndPathFetcher,
+                oneToOneBinder,
+                manyToOneBinder,
+                new PropertyFromValueCreator()
+        )
+        CompositeIdBinder compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder)
+        PropertyBinder propertyBinderHelper = new PropertyBinder()
+        SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinderHelper)
+        IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder)
+        VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinderHelper, BasicValue::new)
+
+        return [
+            propertyBinder: propertyBinder,
+            collectionBinder: collectionBinder,
+            identityBinder: identityBinder,
+            versionBinder: versionBinder,
+            defaultColumnNameFetcher: defaultColumnNameFetcher,
+            columnNameForPropertyAndPathFetcher: columnNameForPropertyAndPathFetcher
+        ]
+    }
+
+    protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+        def binders = getBinders(binder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+    }
+
     void setupSpec() {
         manager.addAllDomainClasses([
                 ListBinderAuthor,
@@ -26,7 +133,8 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         given:
         def collector = getCollector()
         def binder = getGrailsDomainBinder()
-        def collectionBinder = binder.getCollectionBinder()
+        def binders = getBinders(binder)
+        def collectionBinder = binders.collectionBinder
         def namingStrategy = binder.getNamingStrategy()
         def jdbcEnvironment = binder.getJdbcEnvironment()
         def collectionSecondPassBinder = new CollectionSecondPassBinder(
@@ -45,7 +153,7 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = getPersistentEntity(ListBinderBook) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
+        bindRoot(binder, bookEntity, collector, "sessionFactory")
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
index 7d58f13e90..1a921437bb 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
@@ -5,16 +5,123 @@ import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
 
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.SimpleValue
+import org.hibernate.boot.spi.MetadataBuildingContext
+import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableNameFetcher
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder
+import org.grails.orm.hibernate.cfg.GrailsDomainBinder
 
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPassBinder
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.MapSecondPassBinder
 
+import org.grails.orm.hibernate.cfg.domainbinding.binder.OneToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
+import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsPropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.IdentityBinder
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.grails.orm.hibernate.cfg.domainbinding.binder.VersionBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.PropertyBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator
+import org.hibernate.mapping.BasicValue
+
 class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
+    protected Map getBinders(GrailsDomainBinder binder) {
+        MetadataBuildingContext metadataBuildingContext = binder.getMetadataBuildingContext()
+        PersistentEntityNamingStrategy namingStrategy = binder.getNamingStrategy()
+        JdbcEnvironment jdbcEnvironment = binder.getJdbcEnvironment()
+        BackticksRemover backticksRemover = new BackticksRemover()
+        DefaultColumnNameFetcher defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy, backticksRemover)
+        ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher = new ColumnNameForPropertyAndPathFetcher(namingStrategy, defaultColumnNameFetcher, backticksRemover)
+        SimpleValueBinder simpleValueBinder = new SimpleValueBinder(namingStrategy, jdbcEnvironment)
+        EnumTypeBinder enumTypeBinderToUse = new EnumTypeBinder()
+        SimpleValueColumnFetcher simpleValueColumnFetcher = new SimpleValueColumnFetcher()
+        CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder = new CompositeIdentifierToManyToOneBinder(
+                new org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator(),
+                new TableNameFetcher(namingStrategy),
+                namingStrategy,
+                defaultColumnNameFetcher,
+                backticksRemover,
+                simpleValueBinder
+        )
+        OneToOneBinder oneToOneBinder = new OneToOneBinder(namingStrategy, simpleValueBinder)
+        ManyToOneBinder manyToOneBinder = new ManyToOneBinder(namingStrategy, simpleValueBinder, new ManyToOneValuesBinder(), compositeIdentifierToManyToOneBinder, simpleValueColumnFetcher)
+
+        CollectionBinder collectionBinder = new CollectionBinder(
+                metadataBuildingContext,
+                binder,
+                namingStrategy,
+                jdbcEnvironment,
+                simpleValueBinder,
+                enumTypeBinderToUse,
+                manyToOneBinder,
+                compositeIdentifierToManyToOneBinder,
+                simpleValueColumnFetcher
+        )
+        ComponentPropertyBinder componentPropertyBinder = new ComponentPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                jdbcEnvironment,
+                binder.getMappingCacheHolder(),
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                collectionBinder,
+                new PropertyFromValueCreator(),
+                null,
+                simpleValueBinder,
+                oneToOneBinder,
+                manyToOneBinder,
+                columnNameForPropertyAndPathFetcher
+        )
+        GrailsPropertyBinder propertyBinder = new GrailsPropertyBinder(
+                metadataBuildingContext,
+                namingStrategy,
+                binder.getCollectionHolder(),
+                enumTypeBinderToUse,
+                componentPropertyBinder,
+                collectionBinder,
+                simpleValueBinder,
+                columnNameForPropertyAndPathFetcher,
+                oneToOneBinder,
+                manyToOneBinder,
+                new PropertyFromValueCreator()
+        )
+        CompositeIdBinder compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder)
+        PropertyBinder propertyBinderHelper = new PropertyBinder()
+        SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinderHelper)
+        IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder)
+        VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinderHelper, BasicValue::new)
+
+        return [
+            propertyBinder: propertyBinder,
+            collectionBinder: collectionBinder,
+            identityBinder: identityBinder,
+            versionBinder: versionBinder,
+            defaultColumnNameFetcher: defaultColumnNameFetcher,
+            columnNameForPropertyAndPathFetcher: columnNameForPropertyAndPathFetcher
+        ]
+    }
+
+    protected void bindRoot(GrailsDomainBinder binder, GrailsHibernatePersistentEntity entity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) {
+        def binders = getBinders(binder)
+        binder.bindRoot(entity, mappings, sessionFactoryBeanName, binders.defaultColumnNameFetcher, binders.columnNameForPropertyAndPathFetcher, binders.identityBinder as IdentityBinder, binders.versionBinder as VersionBinder, binders.propertyBinder as GrailsPropertyBinder)
+    }
+
     void setupSpec() {
         manager.addAllDomainClasses([
                 MapAuthorBinder,
@@ -26,7 +133,8 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         given:
         def collector = getCollector()
         def binder = getGrailsDomainBinder()
-        def collectionBinder = binder.getCollectionBinder()
+        def binders = getBinders(binder)
+        def collectionBinder = binders.collectionBinder
         def namingStrategy = binder.getNamingStrategy()
         def jdbcEnvironment = binder.getJdbcEnvironment()
         def collectionSecondPassBinder = new CollectionSecondPassBinder(
@@ -45,7 +153,7 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         def bookEntity = getPersistentEntity(MapBookBinder) as GrailsHibernatePersistentEntity
 
         // Register referenced entity in Hibernate
-        binder.bindRoot(bookEntity, collector, "sessionFactory", new DefaultColumnNameFetcher(binder.getNamingStrategy(), new BackticksRemover()))
+        bindRoot(binder, bookEntity, collector, "sessionFactory")
 
         // Manually create RootClass for the main entity
         def rootClass = new RootClass(binder.getMetadataBuildingContext())


(grails-core) 10/16: Refactor single table subclass binding to a dedicated binder class

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

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

commit 1c4d710990901bba1566a5887b0cccbc478e9539
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 11:56:08 2026 -0600

    Refactor single table subclass binding to a dedicated binder class
    
    - Create SingleTableSubclassBinder to handle table-per-hierarchy mapping.
    - Update GrailsDomainBinder to use SingleTableSubclassBinder as a local dependency.
    - Add SingleTableSubclassBinderSpec using real entity classes for comprehensive testing.
---
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 31 ++++-------
 .../binder/SingleTableSubclassBinder.java          | 40 ++++++++++++++
 .../binder/SingleTableSubclassBinderSpec.groovy    | 61 ++++++++++++++++++++++
 3 files changed, 112 insertions(+), 20 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 677c20ade4..e000e4c45d 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -40,6 +40,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.NaturalIdentifierBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder;
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SingleTableSubclassBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.UnionSubclassBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.VersionBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder;
@@ -232,12 +233,13 @@ public class GrailsDomainBinder
         MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder();
         JoinedSubClassBinder joinedSubClassBinder = new JoinedSubClassBinder(metadataBuildingContext, namingStrategy, new SimpleValueColumnBinder(), columnNameForPropertyAndPathFetcher, classBinder);
         UnionSubclassBinder unionSubclassBinder = new UnionSubclassBinder(metadataBuildingContext, namingStrategy, classBinder);
+        SingleTableSubclassBinder singleTableSubclassBinder = new SingleTableSubclassBinder(classBinder);
 
         hibernateMappingContext
                 .getHibernatePersistentEntities(dataSourceName)
                 .stream()
                 .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName))
-                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder));
+                .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
     }
 
 
@@ -268,7 +270,7 @@ public class GrailsDomainBinder
      * @param mappings    The Hibernate Mappings object
      * @param sessionFactoryBeanName  the session factory bean name
      */
-    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
+    protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...]
         if (mappings.getEntityBinding(entity.getName()) != null) {
             LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. ");
             return;
@@ -285,7 +287,7 @@ public class GrailsDomainBinder
                 bindDiscriminatorProperty(root.getTable(), root, m);
             }
             // bind the sub classes
-            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder));
+            children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder));
         }
 
         multiTenantFilterBinder.addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher);
@@ -315,9 +317,9 @@ public class GrailsDomainBinder
                               PersistentClass parent,
                               @Nonnull InFlightMetadataCollector mappings,
                               String sessionFactoryBeanName
-                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder) {
+                            , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder, SingleTableSubclassBinder singleTabl [...]
         mappingCacheHolder.cacheMapping(sub);
-        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder);
+        Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder);
 
 
         parent.addSubclass(subClass);
@@ -328,11 +330,11 @@ public class GrailsDomainBinder
         var children = sub.getChildEntities(dataSourceName);
         if (!children.isEmpty()) {
             // bind the sub classes
-            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder ));
+            children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder, singleTableSubclassBinder ));
         }
     }
 
-    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
+    private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...]
         Subclass subClass;
         subEntity.configureDerivedProperties();
         if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) {
@@ -347,8 +349,8 @@ public class GrailsDomainBinder
         }
         else {
             var singleTableSubclass = new SingleTableSubclass(parent, this.metadataBuildingContext);
-            singleTableSubclass.setDiscriminatorValue(subEntity.getDiscriminatorValue());
-            bindSubClass(subEntity, singleTableSubclass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder, joinedSubClassBinder, unionSubclassBinder);
+
+            singleTableSubclassBinder.bindSubClass(subEntity, singleTableSubclass, mappings);
             subClass = singleTableSubclass;
         }
         subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1));
@@ -370,17 +372,6 @@ public class GrailsDomainBinder
      * @param subClass The Hibernate SubClass instance
      * @param mappings The mappings instance
      */
-    private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, SingleTableSubclass subClass, @Nonnull InFlightMetadataCollector mappings,
-                                String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder, JoinedSubClassBinder joinedSubClassBinder, UnionSubclassBinder unionSubclassBinder) {
-        classBinder.bindClass(sub, subClass, mappings);
-
-        if (LOG.isDebugEnabled())
-            LOG.debug("Mapping subclass: " + subClass.getEntityName() +
-                    " -> " + subClass.getTable().getName());
-
-        // properties
-    }
-
     /**
      * Creates and binds the discriminator property used in table-per-hierarchy inheritance to
      * discriminate between sub class instances
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinder.java
new file mode 100644
index 0000000000..6236c0e5b4
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinder.java
@@ -0,0 +1,40 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder;
+
+import jakarta.annotation.Nonnull;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
+import org.hibernate.boot.spi.InFlightMetadataCollector;
+import org.hibernate.mapping.SingleTableSubclass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Binds a sub-class using table-per-hierarchy inheritance mapping
+ *
+ * @since 7.0
+ */
+public class SingleTableSubclassBinder {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SingleTableSubclassBinder.class);
+
+    private final ClassBinder classBinder;
+
+    public SingleTableSubclassBinder(ClassBinder classBinder) {
+        this.classBinder = classBinder;
+    }
+
+    /**
+     * Binds a sub-class using table-per-hierarchy inheritance mapping
+     *
+     * @param sub      The Grails domain class instance representing the sub-class
+     * @param subClass The Hibernate SubClass instance
+     * @param mappings The mappings instance
+     */
+    public void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, SingleTableSubclass subClass, @Nonnull InFlightMetadataCollector mappings) {
+        classBinder.bindClass(sub, subClass, mappings);
+        subClass.setDiscriminatorValue(sub.getDiscriminatorValue());
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Mapping subclass: " + subClass.getEntityName() +
+                    " -> " + subClass.getTable().getName());
+        }
+    }
+}
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinderSpec.groovy
new file mode 100644
index 0000000000..9e76d4a527
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinderSpec.groovy
@@ -0,0 +1,61 @@
+package org.grails.orm.hibernate.cfg.domainbinding.binder
+
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.Table
+import org.hibernate.mapping.SingleTableSubclass
+
+/**
+ * Tests for SingleTableSubclassBinder using real entity classes.
+ */
+class SingleTableSubclassBinderSpec extends HibernateGormDatastoreSpec {
+
+    SingleTableSubclassBinder binder
+    ClassBinder classBinder = new ClassBinder()
+
+    void setup() {
+        binder = new SingleTableSubclassBinder(classBinder)
+    }
+
+    void "test bind single table subclass with real entities"() {
+        given:
+        def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext()
+        def mappings = buildingContext.getMetadataCollector()
+        
+        // Register entities in mapping context
+        def rootEntity = createPersistentEntity(SingleTableSubClassRoot)
+        def subEntity = createPersistentEntity(SingleTableSubClassSub)
+        
+        // Setup Hibernate RootClass
+        def rootClass = new RootClass(buildingContext)
+        rootClass.setEntityName(SingleTableSubClassRoot.name)
+        def rootTable = new Table("ST_ROOT_TABLE")
+        rootTable.setName("ST_ROOT_TABLE")
+        rootClass.setTable(rootTable)
+        
+        // Setup SingleTableSubclass
+        def singleTableSubclass = new SingleTableSubclass(rootClass, buildingContext)
+        singleTableSubclass.setEntityName(SingleTableSubClassSub.name)
+
+        when:
+        binder.bindSubClass(subEntity, singleTableSubclass, mappings)
+
+        then:
+        singleTableSubclass.getTable() == rootTable
+        singleTableSubclass.getDiscriminatorValue() == "SUB_CLASS"
+    }
+}
+
+@Entity
+class SingleTableSubClassRoot {
+    Long id
+}
+
+@Entity
+class SingleTableSubClassSub extends SingleTableSubClassRoot {
+    String name
+    static mapping = {
+        discriminator "SUB_CLASS"
+    }
+}


(grails-core) 05/16: Refactor second pass classes to remove GrailsDomainBinder dependency

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

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

commit fd8f3ba117d48c64f908a9a189fe0243ec5e3738
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Feb 14 16:52:25 2026 -0600

    Refactor second pass classes to remove GrailsDomainBinder dependency
    
    - Remove GrailsDomainBinder and CollectionBinder from ListSecondPass, MapSecondPass, and SetSecondPass constructors and fields.
    - Remove grailsDomainBinder from CollectionBinder and update its constructor.
    - Update second pass instantiations in CollectionBinder.
    - Update GrailsDomainBinder and unit tests to reflect these changes.
---
 .../org/grails/orm/hibernate/cfg/GrailsDomainBinder.java   |  1 -
 .../cfg/domainbinding/binder/CollectionBinder.java         | 14 +++++---------
 .../cfg/domainbinding/secondpass/ListSecondPass.java       |  8 +-------
 .../cfg/domainbinding/secondpass/MapSecondPass.java        |  8 +-------
 .../cfg/domainbinding/secondpass/SetSecondPass.java        | 10 +---------
 .../cfg/domainbinding/CollectionBinderSpec.groovy          |  1 -
 .../domainbinding/CollectionSecondPassBinderSpec.groovy    |  1 -
 .../cfg/domainbinding/GrailsPropertyBinderSpec.groovy      |  1 -
 .../cfg/domainbinding/ListSecondPassBinderSpec.groovy      |  1 -
 .../cfg/domainbinding/MapSecondPassBinderSpec.groovy       |  1 -
 10 files changed, 8 insertions(+), 38 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index c17485f379..3d35c5a9cc 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -208,7 +208,6 @@ public class GrailsDomainBinder
 
         CollectionBinder collectionBinder = new CollectionBinder(
                 metadataBuildingContext,
-                this,
                 namingStrategy,
                 jdbcEnvironment,
                 simpleValueBinder,
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java
index 7fdd21fd7a..88624ae4ed 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java
@@ -2,7 +2,6 @@ package org.grails.orm.hibernate.cfg.domainbinding.binder;
 
 import jakarta.annotation.Nonnull;
 
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
 import org.grails.orm.hibernate.cfg.GrailsHibernateUtil;
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty;
@@ -40,7 +39,6 @@ public class CollectionBinder {
     private static final Logger LOG = LoggerFactory.getLogger(CollectionBinder.class);
 
     private final MetadataBuildingContext metadataBuildingContext;
-    public final GrailsDomainBinder grailsDomainBinder;
     private final PersistentEntityNamingStrategy namingStrategy;
     private final ListSecondPassBinder listSecondPassBinder;
     private final CollectionSecondPassBinder collectionSecondPassBinder;
@@ -48,7 +46,6 @@ public class CollectionBinder {
 
     public CollectionBinder(
             MetadataBuildingContext metadataBuildingContext,
-            GrailsDomainBinder grailsDomainBinder,
             PersistentEntityNamingStrategy namingStrategy,
             JdbcEnvironment jdbcEnvironment,
             SimpleValueBinder simpleValueBinder,
@@ -57,7 +54,6 @@ public class CollectionBinder {
             CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder,
             SimpleValueColumnFetcher simpleValueColumnFetcher) {
         this.metadataBuildingContext = metadataBuildingContext;
-        this.grailsDomainBinder = grailsDomainBinder;
         this.namingStrategy = namingStrategy;
         this.collectionSecondPassBinder = new CollectionSecondPassBinder(
                 metadataBuildingContext,
@@ -73,8 +69,8 @@ public class CollectionBinder {
         this.mapSecondPassBinder = new MapSecondPassBinder(metadataBuildingContext, namingStrategy, collectionSecondPassBinder);
     }
 
-    public CollectionBinder(MetadataBuildingContext metadataBuildingContext, GrailsDomainBinder grailsDomainBinder, PersistentEntityNamingStrategy namingStrategy, JdbcEnvironment jdbcEnvironment) {
-        this(metadataBuildingContext, grailsDomainBinder, namingStrategy, jdbcEnvironment,
+    public CollectionBinder(MetadataBuildingContext metadataBuildingContext, PersistentEntityNamingStrategy namingStrategy, JdbcEnvironment jdbcEnvironment) {
+        this(metadataBuildingContext, namingStrategy, jdbcEnvironment,
                 new SimpleValueBinder(namingStrategy, jdbcEnvironment),
                 new EnumTypeBinder(),
                 new ManyToOneBinder(namingStrategy, jdbcEnvironment),
@@ -137,13 +133,13 @@ public class CollectionBinder {
 
         // set up second pass
        if (collection instanceof org.hibernate.mapping.List) {
-            mappings.addSecondPass(new ListSecondPass(grailsDomainBinder, this, listSecondPassBinder, property, mappings, collection, sessionFactoryBeanName));
+            mappings.addSecondPass(new ListSecondPass(listSecondPassBinder, property, mappings, collection, sessionFactoryBeanName));
         }
         else if (collection instanceof org.hibernate.mapping.Map) {
-            mappings.addSecondPass(new MapSecondPass(grailsDomainBinder, this, mapSecondPassBinder, property, mappings, collection, sessionFactoryBeanName));
+            mappings.addSecondPass(new MapSecondPass(mapSecondPassBinder, property, mappings, collection, sessionFactoryBeanName));
         }
         else { // Collection -> Bag
-            mappings.addSecondPass(new SetSecondPass(grailsDomainBinder, this,collectionSecondPassBinder,  property, mappings, collection, sessionFactoryBeanName));
+            mappings.addSecondPass(new SetSecondPass(collectionSecondPassBinder,  property, mappings, collection, sessionFactoryBeanName));
         }
     }
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPass.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPass.java
index 761cf4b1b7..ba6ad08a45 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPass.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPass.java
@@ -9,26 +9,20 @@ import org.hibernate.MappingException;
 import org.hibernate.boot.spi.InFlightMetadataCollector;
 import org.hibernate.mapping.Collection;
 
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty;
-import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder;
 
 public class ListSecondPass implements org.hibernate.boot.spi.SecondPass, GrailsSecondPass {
     @Serial
     private static final long serialVersionUID = -3024674993774205193L;
 
-    protected final GrailsDomainBinder grailsDomainBinder;
-    protected final CollectionBinder collectionBinder;
     private final ListSecondPassBinder listSecondPassBinder;
     protected final HibernateToManyProperty property;
     protected final @Nonnull InFlightMetadataCollector mappings;
     protected final Collection collection;
     protected final String sessionFactoryBeanName;
 
-    public ListSecondPass(GrailsDomainBinder grailsDomainBinder, CollectionBinder collectionBinder, ListSecondPassBinder listSecondPassBinder, HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings,
+    public ListSecondPass(ListSecondPassBinder listSecondPassBinder, HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings,
                           Collection coll, String sessionFactoryBeanName) {
-        this.grailsDomainBinder = grailsDomainBinder;
-        this.collectionBinder = collectionBinder;
         this.listSecondPassBinder = listSecondPassBinder;
         this.property = property;
         this.mappings = mappings;
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java
index c0ead55dd0..d6e1a987de 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java
@@ -9,26 +9,20 @@ import org.hibernate.MappingException;
 import org.hibernate.boot.spi.InFlightMetadataCollector;
 import org.hibernate.mapping.Collection;
 
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty;
-import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder;
 
 public class MapSecondPass implements org.hibernate.boot.spi.SecondPass, GrailsSecondPass {
     @Serial
     private static final long serialVersionUID = -3244991685626409031L;
 
-    protected final GrailsDomainBinder grailsDomainBinder;
-    protected final CollectionBinder collectionBinder;
     private final MapSecondPassBinder mapSecondPassBinder;
     protected final HibernateToManyProperty property;
     protected final @Nonnull InFlightMetadataCollector mappings;
     protected final Collection collection;
     protected final String sessionFactoryBeanName;
 
-    public MapSecondPass(GrailsDomainBinder grailsDomainBinder, CollectionBinder collectionBinder, MapSecondPassBinder mapSecondPassBinder, HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings,
+    public MapSecondPass(MapSecondPassBinder mapSecondPassBinder, HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings,
                          Collection coll, String sessionFactoryBeanName) {
-        this.grailsDomainBinder = grailsDomainBinder;
-        this.collectionBinder = collectionBinder;
         this.mapSecondPassBinder = mapSecondPassBinder;
         this.property = property;
         this.mappings = mappings;
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java
index a1e1b38d74..09cc80dfbc 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.java
@@ -8,9 +8,7 @@ import org.hibernate.MappingException;
 import org.hibernate.boot.spi.InFlightMetadataCollector;
 import org.hibernate.mapping.Collection;
 
-import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty;
-import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder;
 
 /**
  * Second pass class for grails relationships. This is required as all
@@ -23,23 +21,17 @@ public class SetSecondPass implements org.hibernate.boot.spi.SecondPass, GrailsS
 
     private static final long serialVersionUID = -5540526942092611348L;
 
-    protected final GrailsDomainBinder grailsDomainBinder;
-    protected final CollectionBinder collectionBinder;
     private final CollectionSecondPassBinder collectionSecondPassBinder;
     protected final HibernateToManyProperty property;
     protected final @Nonnull InFlightMetadataCollector mappings;
     protected final Collection collection;
     protected final String sessionFactoryBeanName;
 
-    public SetSecondPass(GrailsDomainBinder grailsDomainBinder,
-                         CollectionBinder collectionBinder,
-                         CollectionSecondPassBinder collectionSecondPassBinder,
+    public SetSecondPass(CollectionSecondPassBinder collectionSecondPassBinder,
                          HibernateToManyProperty property,
                          @Nonnull InFlightMetadataCollector mappings,
                          Collection coll,
                          String sessionFactoryBeanName) {
-        this.grailsDomainBinder = grailsDomainBinder;
-        this.collectionBinder = collectionBinder;
         this.collectionSecondPassBinder = collectionSecondPassBinder;
         this.property = property;
         this.mappings = mappings;
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
index 49e798674e..84aaf1f6be 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinderSpec.groovy
@@ -62,7 +62,6 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
 
         CollectionBinder collectionBinder = new CollectionBinder(
                 metadataBuildingContext,
-                binder,
                 namingStrategy,
                 jdbcEnvironment,
                 simpleValueBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
index b9ad7a642f..2bf547279e 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionSecondPassBinderSpec.groovy
@@ -64,7 +64,6 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         CollectionBinder collectionBinder = new CollectionBinder(
                 metadataBuildingContext,
-                binder,
                 namingStrategy,
                 jdbcEnvironment,
                 simpleValueBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
index 63b2def531..e90f4c1a0b 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsPropertyBinderSpec.groovy
@@ -68,7 +68,6 @@ class GrailsPropertyBinderSpec extends HibernateGormDatastoreSpec {
 
         CollectionBinder collectionBinder = new CollectionBinder(
                 metadataBuildingContext,
-                binder,
                 namingStrategy,
                 jdbcEnvironment,
                 simpleValueBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
index a850f89cc0..1270d45e57 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
@@ -66,7 +66,6 @@ class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         CollectionBinder collectionBinder = new CollectionBinder(
                 metadataBuildingContext,
-                binder,
                 namingStrategy,
                 jdbcEnvironment,
                 simpleValueBinder,
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
index 5803d6508c..e07ffd467f 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
@@ -66,7 +66,6 @@ class MapSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         CollectionBinder collectionBinder = new CollectionBinder(
                 metadataBuildingContext,
-                binder,
                 namingStrategy,
                 jdbcEnvironment,
                 simpleValueBinder,


(grails-core) 14/16: Refactor natural identifier binding to support composite primary keys and fix mapping DSL configuration for natural identifiers.

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

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

commit 34866ea7fa1a024c3870bb47712ddef45b6cf0c4
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Feb 15 17:09:37 2026 -0600

    Refactor natural identifier binding to support composite primary keys and fix mapping DSL configuration for natural identifiers.
---
 .../orm/hibernate/cfg/CompositeIdentity.groovy     | 17 ++++++++
 .../org/grails/orm/hibernate/cfg/Identity.groovy   |  3 +-
 .../binder/NaturalIdentifierBinder.java            |  5 +--
 .../domainbinding/hibernate/HibernateIdentity.java | 12 ++++++
 .../hibernate/HibernateMappingBuilder.groovy       | 46 +++++++++++-----------
 .../NaturalIdentifierBinderSpec.groovy             | 33 ++++++++++++++++
 6 files changed, 89 insertions(+), 27 deletions(-)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy
index bdb85a70b2..b39454ddc3 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy
@@ -40,4 +40,21 @@ class CompositeIdentity extends Property implements HibernateIdentity {
      * The composite id class
      */
     Class compositeClass
+    /**
+     * The natural id definition
+     */
+    NaturalId natural
+
+    /**
+     * Define the natural id
+     * @param naturalIdDef The callable
+     * @return This id
+     */
+    CompositeIdentity naturalId(@DelegatesTo(NaturalId) Closure naturalIdDef) {
+        this.natural = new NaturalId()
+        naturalIdDef.setDelegate(this.natural)
+        naturalIdDef.setResolveStrategy(Closure.DELEGATE_ONLY)
+        naturalIdDef.call()
+        return this
+    }
 }
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy
index 87fdc352a4..45039669ce 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy
@@ -80,7 +80,8 @@ class Identity extends Property implements HibernateIdentity {
      * @return This id
      */
     Identity naturalId(@DelegatesTo(NaturalId) Closure naturalIdDef) {
-        naturalIdDef.setDelegate(new NaturalId())
+        this.natural = new NaturalId()
+        naturalIdDef.setDelegate(this.natural)
         naturalIdDef.setResolveStrategy(Closure.DELEGATE_ONLY)
         naturalIdDef.call()
         return this
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java
index 0dec49abab..b27f4c1e8d 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java
@@ -3,6 +3,7 @@ package org.grails.orm.hibernate.cfg.domainbinding.binder;
 import org.apache.commons.collections.CollectionUtils;
 import org.grails.orm.hibernate.cfg.Identity;
 import org.grails.orm.hibernate.cfg.Mapping;
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateIdentity;
 import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator;
 
 import org.hibernate.mapping.PersistentClass;
@@ -26,9 +27,7 @@ public class NaturalIdentifierBinder {
 
     public void bindNaturalIdentifier(Mapping mapping, PersistentClass persistentClass) {
         Optional.ofNullable(mapping.getIdentity())
-                .filter(Identity.class::isInstance)
-                .map(Identity.class::cast)
-                .map(Identity::getNatural)
+                .map(HibernateIdentity::getNatural)
                 .ifPresent(naturalId -> {
                     if(CollectionUtils.isEmpty(naturalId.getPropertyNames())) {
                         return;
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java
index 2694ce20fd..3e1355a7c9 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java
@@ -1,7 +1,19 @@
 package org.grails.orm.hibernate.cfg.domainbinding.hibernate;
 
+import org.grails.orm.hibernate.cfg.NaturalId;
+
 /**
  * A marker interface for single and composite identity configurations in GORM for Hibernate.
  */
 public interface HibernateIdentity {
+    /**
+     * @return The natural id definition
+     */
+    NaturalId getNatural();
+
+    /**
+     * Sets the natural id definition
+     * @param natural The natural id definition
+     */
+    void setNatural(NaturalId natural);
 }
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy
index 7fc2f09ca6..048ccc2489 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy
@@ -28,6 +28,7 @@ import jakarta.persistence.AccessType
 import org.grails.orm.hibernate.cfg.CacheConfig
 import org.grails.orm.hibernate.cfg.ColumnConfig
 import org.grails.orm.hibernate.cfg.CompositeIdentity
+import org.grails.orm.hibernate.cfg.Identity
 import org.grails.orm.hibernate.cfg.JoinTable
 import org.grails.orm.hibernate.cfg.Mapping
 import org.grails.orm.hibernate.cfg.NaturalId
@@ -393,43 +394,42 @@ class HibernateMappingBuilder implements MappingConfigurationBuilder<Mapping, Pr
         if (args.composite) {
             mapping.identity = new CompositeIdentity(propertyNames:args.composite as String[])
             if (args.compositeClass) {
-                mapping.identity.compositeClass = args.compositeClass
+                ((CompositeIdentity)mapping.identity).compositeClass = (Class)args.compositeClass
             }
         }
         else {
             if (args?.generator) {
-                mapping.identity.generator = args.remove('generator')
+                ((Identity)mapping.identity).generator = args.remove('generator').toString()
             }
             if (args?.name) {
-                mapping.identity.name = args.remove('name').toString()
+                ((Identity)mapping.identity).name = args.remove('name').toString()
             }
             if (args?.params) {
-                def params = args.remove('params')
+                def params = (Map)args.remove('params')
                 for (entry in params) {
                     params[entry.key] = entry.value?.toString()
                 }
-                mapping.identity.params = params
+                ((Identity)mapping.identity).params = params
             }
-            if (args?.natural) {
-                def naturalArgs = args.remove('natural')
-                def propertyNames = naturalArgs instanceof Map ? naturalArgs.remove('properties') : naturalArgs
-
-                if (propertyNames) {
-                    def ni = new NaturalId()
-                    ni.mutable = (naturalArgs instanceof Map) && naturalArgs.mutable ?: false
-                    if (propertyNames instanceof List) {
-                        ni.propertyNames = propertyNames
-                    }
-                    else {
-                        ni.propertyNames = [propertyNames.toString()]
-                    }
-                    mapping.identity.natural = ni
+        }
+        if (args?.natural) {
+            def naturalArgs = args.remove('natural')
+            def propertyNames = naturalArgs instanceof Map ? ((Map)naturalArgs).remove('properties') : naturalArgs
+
+            if (propertyNames) {
+                def ni = new NaturalId()
+                ni.mutable = (naturalArgs instanceof Map) && ((Map)naturalArgs).mutable ?: false
+                if (propertyNames instanceof List) {
+                    ni.propertyNames = (List<String>)propertyNames
                 }
+                else {
+                    ni.propertyNames = [propertyNames.toString()]
+                }
+                mapping.identity.natural = ni
             }
-            // still more arguments?
-            if (args) {
-                handleMethodMissing("id", [args] as Object[])
-            }
+        }
+        if (!args.composite && args) {
+            handleMethodMissing("id", [args] as Object[])
         }
     }
 
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy
index 036d04e14b..6abb9c8a30 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy
@@ -1,6 +1,7 @@
 package org.grails.orm.hibernate.cfg.domainbinding
 
 import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.CompositeIdentity
 import org.grails.orm.hibernate.cfg.Identity
 import org.grails.orm.hibernate.cfg.Mapping
 import org.grails.orm.hibernate.cfg.NaturalId
@@ -103,6 +104,38 @@ class NaturalIdentifierBinderSpec extends HibernateGormDatastoreSpec {
         }
     }
 
+    void "test bindNaturalIdentifier with CompositeIdentity"() {
+        given:
+        def mapping = Mock(Mapping.class)
+        def identity = Mock(CompositeIdentity)
+        def naturalId = Mock(NaturalId)
+        def property = new Property()
+        property.setName("id1")
+        def value = Mock(Value)
+        property.setValue(value)
+        Table table = Mock(Table)
+        def id1 = "id1"
+        mapping.getIdentity() >> identity
+        identity.getNatural() >> naturalId
+        naturalId.getPropertyNames() >> [id1]
+        naturalId.isMutable() >> false
+        def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        rootClass.addProperty(property)
+        rootClass.setTable(table)
+        value.getSelectables() >> []
+
+        def uniqueNameGenerator = Mock(UniqueNameGenerator)
+        def binder = new NaturalIdentifierBinder(uniqueNameGenerator)
+
+        when:
+        binder.bindNaturalIdentifier(mapping, rootClass)
+
+        then:
+        1 * uniqueNameGenerator.setGeneratedUniqueName(_)
+        property.isNaturalIdentifier()
+        1 * table.addUniqueKey(_)
+    }
+
     void "test bindNaturalIdentifier when no natural id is defined"() {
         given:
         def mapping = Mock(Mapping.class)