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 ae067982e535d979c2fc73d4c053e505189c265f Author: Walter Duque de Estrada <[email protected]> AuthorDate: Fri Feb 6 19:30:32 2026 -0600 progress --- .../orm/hibernate/cfg/GrailsDomainBinder.java | 24 ++--- .../cfg/domainbinding/CollectionBinder.java | 82 +------------- .../cfg/domainbinding/ListSecondPassBinder.java | 118 +++++++++++++++++++++ .../domainbinding/secondpass/ListSecondPass.java | 5 - .../domainbinding/ListSecondPassBinderSpec.groovy | 83 +++++++++++++++ 5 files changed, 211 insertions(+), 101 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 9e8769918c..67d5275179 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 @@ -48,7 +48,6 @@ import org.hibernate.mapping.Column; import org.hibernate.mapping.DependantValue; import org.hibernate.mapping.Formula; import org.hibernate.mapping.JoinedSubclass; -import org.hibernate.mapping.OneToMany; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; @@ -305,7 +304,7 @@ public class GrailsDomainBinder children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder )); } - addMultiTenantFilterIfNecessary(entity, root, mappings, sessionFactoryBeanName); + addMultiTenantFilterIfNecessary(entity, root, mappings); mappings.addEntityBinding(root); } @@ -320,14 +319,13 @@ public class GrailsDomainBinder /** * Add a Hibernate filter for multitenancy if the persistent class is multitenant * - * @param entity target persistent entity for get tenant information + * @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 - * @param sessionFactoryBeanName the session factory bean name + * @param mappings mappings to add the filter */ private void addMultiTenantFilterIfNecessary( @Nonnull GrailsHibernatePersistentEntity entity, PersistentClass persistentClass, - @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName) { + @Nonnull InFlightMetadataCollector mappings) { if (entity.isMultiTenant()) { TenantId tenantId = entity.getTenantId(); @@ -380,7 +378,7 @@ public class GrailsDomainBinder parent.addSubclass(subClass); mappings.addEntityBinding(subClass); - addMultiTenantFilterIfNecessary(sub, subClass, mappings, sessionFactoryBeanName); + addMultiTenantFilterIfNecessary(sub, subClass, mappings); var children = sub.getChildEntities(dataSourceName); if (!children.isEmpty()) { @@ -674,10 +672,7 @@ public class GrailsDomainBinder new NaturalIdentifierBinder().bindNaturalIdentifier(domainClass.getMappedForm(), persistentClass); } - private void bindOneToMany(org.grails.datastore.mapping.model.types.OneToMany currentGrailsProp, OneToMany one, @Nonnull InFlightMetadataCollector mappings) { - one.setReferencedEntityName(currentGrailsProp.getAssociatedEntity().getName()); - one.setIgnoreNotFound(true); - } + public MetadataBuildingContext getMetadataBuildingContext() { return metadataBuildingContext; @@ -691,13 +686,6 @@ public class GrailsDomainBinder return collectionHolder; } - public EnumTypeBinder getEnumTypeBinder() { - return enumTypeBinder; - } - - public ComponentPropertyBinder getComponentPropertyBinder() { - return componentPropertyBinder; - } public PropertyFromValueCreator getPropertyFromValueCreator() { return propertyFromValueCreator; diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinder.java index c969a7f25b..5641a71411 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionBinder.java @@ -36,7 +36,6 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.DependantValue; -import org.hibernate.mapping.IndexBackref; import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.ManyToOne; @@ -64,7 +63,6 @@ import java.util.Optional; import java.util.Set; import java.util.StringTokenizer; -import static java.util.Optional.ofNullable; import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.*; /** @@ -77,11 +75,13 @@ public class CollectionBinder { private final MetadataBuildingContext metadataBuildingContext; private final GrailsDomainBinder grailsDomainBinder; private final PersistentEntityNamingStrategy namingStrategy; + private final ListSecondPassBinder listSecondPassBinder; public CollectionBinder(MetadataBuildingContext metadataBuildingContext, GrailsDomainBinder grailsDomainBinder, PersistentEntityNamingStrategy namingStrategy) { this.metadataBuildingContext = metadataBuildingContext; this.grailsDomainBinder = grailsDomainBinder; this.namingStrategy = namingStrategy; + this.listSecondPassBinder = new ListSecondPassBinder(metadataBuildingContext, this); } /** @@ -316,81 +316,7 @@ public class CollectionBinder { public void bindListSecondPass(HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, Map<?, ?> persistentClasses, org.hibernate.mapping.List list, String sessionFactoryBeanName) { - - bindCollectionSecondPass(property, mappings, persistentClasses, list, sessionFactoryBeanName); - - String columnName = getIndexColumnName(property); - final boolean isManyToMany = property instanceof ManyToMany; - - if (isManyToMany && !property.isOwningSide()) { - throw new MappingException("Invalid association [" + property + - "]. List collection types only supported on the owning side of a many-to-many relationship."); - } - - Table collectionTable = list.getCollectionTable(); - SimpleValue iv = new BasicValue(metadataBuildingContext, collectionTable); - String type = ((GrailsHibernatePersistentProperty) property).getIndexColumnType("integer"); - new SimpleValueColumnBinder().bindSimpleValue(iv, type, columnName, true); - iv.setTypeName(type); - list.setIndex(iv); - list.setBaseIndex(0); - list.setInverse(false); - - Value v = list.getElement(); - v.createForeignKey(); - - if (property.isBidirectional()) { - - String entityName; - Value element = list.getElement(); - if (element instanceof ManyToOne) { - ManyToOne manyToOne = (ManyToOne) element; - entityName = manyToOne.getReferencedEntityName(); - } else { - entityName = ((OneToMany) element).getReferencedEntityName(); - } - - PersistentClass referenced = mappings.getEntityBinding(entityName); - - boolean compositeIdProperty = property.getInverseSide().isCompositeIdProperty(); - if (!compositeIdProperty) { - Backref prop = new Backref(); - final PersistentEntity owner = property.getOwner(); - prop.setEntityName(owner.getName()); - String s2 = property.getName(); - prop.setName(UNDERSCORE + new BackticksRemover().apply(owner.getJavaClass().getSimpleName()) + UNDERSCORE + new BackticksRemover().apply(s2) + "Backref"); - prop.setSelectable(false); - prop.setUpdatable(false); - if (isManyToMany) { - prop.setInsertable(false); - } - prop.setCollectionRole(list.getRole()); - prop.setValue(list.getKey()); - - DependantValue value = (DependantValue) prop.getValue(); - if (!property.isCircular()) { - value.setNullable(false); - } - value.setUpdateable(true); - prop.setOptional(false); - - referenced.addProperty(prop); - } - - if ((!list.getKey().isNullable() && !list.isInverse()) || compositeIdProperty) { - IndexBackref ib = new IndexBackref(); - ib.setName(UNDERSCORE + property.getName() + "IndexBackref"); - ib.setUpdatable(false); - ib.setSelectable(false); - if (isManyToMany) { - ib.setInsertable(false); - } - ib.setCollectionRole(list.getRole()); - ib.setEntityName(list.getOwner().getEntityName()); - ib.setValue(list.getIndex()); - referenced.addProperty(ib); - } - } + listSecondPassBinder.bindListSecondPass(property, mappings, persistentClasses, list, sessionFactoryBeanName); } public void bindMapSecondPass(HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, @@ -501,7 +427,7 @@ public class CollectionBinder { tableName, null, false, metadataBuildingContext)); } - private String getIndexColumnName(PersistentProperty property) { + public String getIndexColumnName(PersistentProperty property) { PropertyConfig pc = property instanceof GrailsHibernatePersistentProperty ghpp ? ghpp.getMappedForm() : new PropertyConfig(); if (pc.getIndexColumn() != null && pc.getIndexColumn().getColumn() != null) { return pc.getIndexColumn().getColumn(); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinder.java new file mode 100644 index 0000000000..08627e1410 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinder.java @@ -0,0 +1,118 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import jakarta.annotation.Nonnull; +import org.grails.datastore.mapping.model.PersistentEntity; +import org.grails.datastore.mapping.model.types.ManyToMany; +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty; +import org.grails.orm.hibernate.cfg.HibernateToManyProperty; +import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy; +import org.hibernate.MappingException; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.mapping.Backref; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.DependantValue; +import org.hibernate.mapping.IndexBackref; +import org.hibernate.mapping.ManyToOne; +import org.hibernate.mapping.OneToMany; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.Value; + +import java.util.Map; + +import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.UNDERSCORE; + +/** + * Refactored from CollectionBinder to handle list second pass binding. + */ +public class ListSecondPassBinder { + + private final MetadataBuildingContext metadataBuildingContext; + private final CollectionBinder collectionBinder; + + public ListSecondPassBinder(MetadataBuildingContext metadataBuildingContext, CollectionBinder collectionBinder) { + this.metadataBuildingContext = metadataBuildingContext; + this.collectionBinder = collectionBinder; + } + + public void bindListSecondPass(HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, + Map<?, ?> persistentClasses, org.hibernate.mapping.List list, String sessionFactoryBeanName) { + + collectionBinder.bindCollectionSecondPass(property, mappings, persistentClasses, list, sessionFactoryBeanName); + + String columnName = collectionBinder.getIndexColumnName(property); + final boolean isManyToMany = property instanceof ManyToMany; + + if (isManyToMany && !property.isOwningSide()) { + throw new MappingException("Invalid association [" + property + + "]. List collection types only supported on the owning side of a many-to-many relationship."); + } + + Table collectionTable = list.getCollectionTable(); + SimpleValue iv = new BasicValue(metadataBuildingContext, collectionTable); + String type = ((GrailsHibernatePersistentProperty) property).getIndexColumnType("integer"); + new SimpleValueColumnBinder().bindSimpleValue(iv, type, columnName, true); + iv.setTypeName(type); + list.setIndex(iv); + list.setBaseIndex(0); + list.setInverse(false); + + Value v = list.getElement(); + v.createForeignKey(); + + if (property.isBidirectional()) { + + String entityName; + Value element = list.getElement(); + if (element instanceof ManyToOne) { + ManyToOne manyToOne = (ManyToOne) element; + entityName = manyToOne.getReferencedEntityName(); + } else { + entityName = ((OneToMany) element).getReferencedEntityName(); + } + + PersistentClass referenced = mappings.getEntityBinding(entityName); + + boolean compositeIdProperty = property.getInverseSide().isCompositeIdProperty(); + if (!compositeIdProperty) { + Backref prop = new Backref(); + final PersistentEntity owner = property.getOwner(); + prop.setEntityName(owner.getName()); + String s2 = property.getName(); + prop.setName(UNDERSCORE + new BackticksRemover().apply(owner.getJavaClass().getSimpleName()) + UNDERSCORE + new BackticksRemover().apply(s2) + "Backref"); + prop.setSelectable(false); + prop.setUpdatable(false); + if (isManyToMany) { + prop.setInsertable(false); + } + prop.setCollectionRole(list.getRole()); + prop.setValue(list.getKey()); + + DependantValue value = (DependantValue) prop.getValue(); + if (!property.isCircular()) { + value.setNullable(false); + } + value.setUpdateable(true); + prop.setOptional(false); + + referenced.addProperty(prop); + } + + if ((!list.getKey().isNullable() && !list.isInverse()) || compositeIdProperty) { + IndexBackref ib = new IndexBackref(); + ib.setName(UNDERSCORE + property.getName() + "IndexBackref"); + ib.setUpdatable(false); + ib.setSelectable(false); + if (isManyToMany) { + ib.setInsertable(false); + } + ib.setCollectionRole(list.getRole()); + ib.setEntityName(list.getOwner().getEntityName()); + ib.setValue(list.getIndex()); + referenced.addProperty(ib); + } + } + } +} 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 fcb12b9bfb..bf5f8edf7f 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 @@ -20,11 +20,6 @@ public class ListSecondPass extends GrailsCollectionSecondPass { super(grailsDomainBinder, property, mappings, coll, sessionFactoryBeanName); } - @Override - public void doSecondPass(Map<?, ?> persistentClasses, Map<?, ?> inheritedMetas) throws MappingException { - grailsDomainBinder.bindListSecondPass(property, mappings, persistentClasses, - (org.hibernate.mapping.List) collection, sessionFactoryBeanName); - } @SuppressWarnings("rawtypes") @Override 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 new file mode 100644 index 0000000000..8f872682d4 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy @@ -0,0 +1,83 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import grails.gorm.annotation.Entity +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity +import org.grails.orm.hibernate.cfg.HibernateToManyProperty + +import org.hibernate.mapping.RootClass +import org.hibernate.mapping.SimpleValue + +class ListSecondPassBinderSpec extends HibernateGormDatastoreSpec { + + void setupSpec() { + manager.addAllDomainClasses([ + ListBinderAuthor, + ListBinderBook + ]) + } + + void "Test bind list second pass"() { + given: + def collector = getCollector() + def binder = getGrailsDomainBinder() + def collectionBinder = binder.getCollectionBinder() + def listSecondPassBinder = new ListSecondPassBinder(binder.getMetadataBuildingContext(), collectionBinder) + + def authorEntity = getPersistentEntity(ListBinderAuthor) as GrailsHibernatePersistentEntity + def bookEntity = getPersistentEntity(ListBinderBook) as GrailsHibernatePersistentEntity + + // Register referenced entity in Hibernate + binder.bindRoot(bookEntity, collector, "sessionFactory") + + // Manually create RootClass for the main entity + def rootClass = new RootClass(binder.getMetadataBuildingContext()) + rootClass.setEntityName(authorEntity.name) + rootClass.setJpaEntityName(authorEntity.name) + rootClass.setTable(collector.addTable(null, null, "LIST_BINDER_AUTHOR", null, false, binder.getMetadataBuildingContext())) + + // Add a primary key to avoid NPE + def pk = new org.hibernate.mapping.PrimaryKey(rootClass.table) + def idCol = new org.hibernate.mapping.Column("id") + rootClass.table.addColumn(idCol) + pk.addColumn(idCol) + rootClass.table.setPrimaryKey(pk) + collector.addEntityBinding(rootClass) + + def booksProp = authorEntity.getPropertyByName("books") as HibernateToManyProperty + def list = new org.hibernate.mapping.List(binder.getMetadataBuildingContext(), rootClass) + list.setRole(authorEntity.name + ".books") + + // Initial first pass binding needed for second pass to work + collectionBinder.bindCollection(booksProp, list, rootClass, collector, "", "sessionFactory") + + // Prepare persistentClasses map + Map persistentClasses = [ + (authorEntity.name): rootClass, + (bookEntity.name): collector.getEntityBinding(bookEntity.name) + ] + + when: + listSecondPassBinder.bindListSecondPass(booksProp, collector, persistentClasses, list, "sessionFactory") + collector.processSecondPasses(binder.getMetadataBuildingContext()) + + then: + list.index != null + list.index instanceof SimpleValue + ((SimpleValue)list.index).typeName == "integer" + list.element != null + } +} + +@Entity +class ListBinderAuthor { + Long id + java.util.List<ListBinderBook> books + static hasMany = [books: ListBinderBook] +} + +@Entity +class ListBinderBook { + Long id + String title +}
