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 <[email protected]>
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
+    }
+}

Reply via email to