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