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/04/06 19:16:59 UTC

(grails-core) 09/12: H7: encapsulate collection configuration within HibernateToManyProperty

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

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

commit 00e5bb8fedbbed3936c3c66da3656e9ca952442d
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Apr 5 19:36:27 2026 -0500

    H7: encapsulate collection configuration within HibernateToManyProperty
---
 .../cfg/domainbinding/binder/CollectionBinder.java | 18 +--------
 .../hibernate/HibernateBasicProperty.java          |  4 +-
 .../HibernateEmbeddedCollectionProperty.java       |  4 +-
 .../hibernate/HibernateManyToManyProperty.java     |  4 +-
 .../hibernate/HibernateOneToManyProperty.java      |  4 +-
 .../hibernate/HibernateToManyProperty.java         | 28 +++++++++++++-
 .../cfg/domainbinding/CollectionBinderSpec.groovy  | 44 ++++++++++++++--------
 .../TableForManyCalculatorSpec.groovy              |  3 ++
 .../HibernateManyToManyPropertySpec.groovy         | 37 ++++++++++++++++++
 9 files changed, 105 insertions(+), 41 deletions(-)

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 04d91e5381..21f840708e 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
@@ -131,9 +131,8 @@ public class CollectionBinder {
      * @return the result
      */
     public Collection bindCollection(HibernateToManyProperty property, String path) {
-        Collection collection = initializeCollection(property, path);
-
-        configureCollectionMetadata(property, collection);
+        Collection collection = collectionHolder.create(property);
+        property.setCollection(collection, path);
 
         if (property.shouldBindWithForeignKey()) {
             bindOneToManyElement((HibernateToManyEntityProperty) property, collection);
@@ -148,19 +147,6 @@ public class CollectionBinder {
         return collection;
     }
 
-    private Collection initializeCollection(HibernateToManyProperty property, String path) {
-        Collection collection = collectionHolder.create(property);
-        property.setCollection(collection);
-        collection.setRole(property.getRole(path));
-        return collection;
-    }
-
-    private void configureCollectionMetadata(HibernateToManyProperty property, Collection collection) {
-        collection.setFetchMode(property.getFetchMode());
-        collection.setOrphanDelete(ALL_DELETE_ORPHAN.getValue().equals(property.getCascade()));
-        collection.setBatchSize(property.getBatchSize());
-    }
-
     private void bindOneToManyElement(HibernateToManyEntityProperty property, Collection collection) {
         OneToMany oneToMany = new OneToMany(metadataBuildingContext, collection.getOwner());
         oneToMany.setReferencedEntityName(property.getHibernateAssociatedEntity().getName());
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java
index 565781defc..7aa1f98a41 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java
@@ -37,12 +37,12 @@ public class HibernateBasicProperty extends BasicWithMapping<PropertyConfig> imp
     }
 
     @Override
-    public Collection getCollection() {
+    public Collection getHibernateCollection() {
         return collection;
     }
 
     @Override
-    public void setCollection(Collection collection) {
+    public void setHibernateCollection(Collection collection) {
         this.collection = collection;
     }
 }
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java
index 0769b42ba6..3ddc4e4b7b 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java
@@ -41,12 +41,12 @@ public class HibernateEmbeddedCollectionProperty extends EmbeddedCollectionWithM
     }
 
     @Override
-    public Collection getCollection() {
+    public Collection getHibernateCollection() {
         return collection;
     }
 
     @Override
-    public void setCollection(Collection collection) {
+    public void setHibernateCollection(Collection collection) {
         this.collection = collection;
     }
 }
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java
index 488655ba96..6192c85bde 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java
@@ -48,12 +48,12 @@ public class HibernateManyToManyProperty extends ManyToManyWithMapping<PropertyC
     }
 
     @Override
-    public Collection getCollection() {
+    public Collection getHibernateCollection() {
         return collection;
     }
 
     @Override
-    public void setCollection(Collection collection) {
+    public void setHibernateCollection(Collection collection) {
         this.collection = collection;
     }
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java
index 02654871cb..88579c512b 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java
@@ -50,12 +50,12 @@ public class HibernateOneToManyProperty extends OneToManyWithMapping<PropertyCon
     }
 
     @Override
-    public Collection getCollection() {
+    public Collection getHibernateCollection() {
         return collection;
     }
 
     @Override
-    public void setCollection(Collection collection) {
+    public void setHibernateCollection(Collection collection) {
         this.collection = collection;
     }
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
index a4628ca94c..15cc2e5af1 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
@@ -23,6 +23,7 @@ import java.util.Optional;
 
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.hibernate.FetchMode;
+import org.hibernate.MappingException;
 import org.hibernate.mapping.Collection;
 import org.hibernate.mapping.IndexedCollection;
 
@@ -41,6 +42,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover;
 import static java.util.Optional.ofNullable;
 import static org.grails.orm.hibernate.cfg.GrailsHibernateUtil.qualify;
 import static org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsDomainBinder.UNDERSCORE;
+import static org.grails.orm.hibernate.cfg.domainbinding.util.CascadeBehavior.ALL_DELETE_ORPHAN;
 
 /** Marker interface for Hibernate to-many associations */
 public interface HibernateToManyProperty extends PropertyWithMapping<PropertyConfig>, HibernateAssociation {
@@ -246,9 +248,31 @@ public interface HibernateToManyProperty extends PropertyWithMapping<PropertyCon
         return isNullable();
     }
 
-    Collection getCollection();
+    default Collection getCollection() {
+        Collection collection = getHibernateCollection();
+        if (collection == null) {
+            throw new MappingException("Hibernate Collection has not been initialized for property [" + getName() + "]. Call setCollection() first.");
+        }
+        return collection;
+    }
+
+    default void setCollection(Collection collection) {
+        setCollection(collection, "");
+    }
+
+    default void setCollection(Collection collection, String path) {
+        if (collection != null) {
+            collection.setRole(getRole(path));
+            collection.setFetchMode(getFetchMode());
+            collection.setOrphanDelete(ALL_DELETE_ORPHAN.getValue().equals(getCascade()));
+            collection.setBatchSize(getBatchSize());
+        }
+        setHibernateCollection(collection);
+    }
+
+    Collection getHibernateCollection();
 
-    void setCollection(Collection collection);
+    void setHibernateCollection(Collection collection);
 
     default String getCascade() {
         return getHibernateMappedForm().getCascade();
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 9e6868f117..e7df64fe24 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
@@ -28,10 +28,13 @@ import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
 import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyEntityProperty
 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.SimpleValueColumnFetcher
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+import org.grails.orm.hibernate.cfg.domainbinding.util.TableForManyCalculator
 import org.hibernate.boot.spi.InFlightMetadataCollector
 import org.hibernate.mapping.OneToMany
 import org.hibernate.mapping.Table
@@ -40,6 +43,7 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
 
     CollectionBinder binder
     InFlightMetadataCollector mockCollector
+    TableForManyCalculator mockCalculator
 
     void setup() {
         def gdb = getGrailsDomainBinder()
@@ -51,7 +55,11 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
             getBootstrapContext() >> mbc.getMetadataCollector().getBootstrapContext()
             getDatabase() >> mbc.getMetadataCollector().getDatabase()
             addTable(_, _, _, _, _, _) >> { schema, catalog, name, sub, isAbstract, context ->
-                return new Table("test", name)
+                return new Table("test", name).with {
+                    setSchema(schema)
+                    setCatalog(catalog)
+                    return it
+                }
             }
         }
         
@@ -64,44 +72,50 @@ class CollectionBinderSpec extends HibernateGormDatastoreSpec {
         def citmto = new CompositeIdentifierToManyToOneBinder(new org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator(), ns, dcnf, backticksRemover, svb)
         def mtob = new ManyToOneBinder(mbc, ns, svb, new ManyToOneValuesBinder(), citmto)
         def ch = new CollectionHolder(mbc)
-        def tfmc = new org.grails.orm.hibernate.cfg.domainbinding.util.TableForManyCalculator(ns, mockCollector)
+        mockCalculator = Mock(TableForManyCalculator)
 
-        binder = new CollectionBinder(mbc, ns, svb, etb, mtob, citmto, svcf, ch, mockCollector, tfmc)
+        binder = new CollectionBinder(mbc, ns, svb, etb, mtob, citmto, svcf, ch, mockCollector, mockCalculator)
     }
 
     def setupSpec() {
         manager.addAllDomainClasses([Person, Pet, CBManyToManyA, CBManyToManyB])
     }
 
-    void "test bindCollection for one-to-many"() {
+    void "test bindCollection delegates configuration to property.setCollection"() {
         given:
         def personEntity = mappingContext.getPersistentEntity(Person.name) as GrailsHibernatePersistentEntity
-        def petsProp = personEntity.getPropertyByName("pets")
+        def petsProp = personEntity.getPropertyByName("pets") as HibernateToManyEntityProperty
 
         when:
-        def collection = binder.bindCollection(petsProp, "")
+        def collection = binder.bindCollection(petsProp, "my.path")
 
         then:
         1 * mockCollector.addCollectionBinding(_)
-        collection.role == "${Person.name}.pets".toString()
-        collection.element instanceof OneToMany
-        (collection.element as OneToMany).referencedEntityName == Pet.name
+        collection.role == "${Person.name}.my.path.pets".toString()
+        collection.fetchMode == petsProp.getFetchMode()
+        collection.batchSize == petsProp.getBatchSize()
+        
+        and: "Property has been initialized"
+        petsProp.getCollection() == collection
     }
 
-    void "test bindCollection for many-to-many uses join table"() {
+    void "test bindCollection for many-to-many uses calculator"() {
         given:
         def entityA = mappingContext.getPersistentEntity(CBManyToManyA.name) as GrailsHibernatePersistentEntity
-        def othersProp = entityA.getPropertyByName("others")
+        def othersProp = entityA.getPropertyByName("others") as HibernateToManyProperty
+        
+        mockCalculator.getTableName(othersProp) >> "custom_join_table"
+        mockCalculator.getJoinTableSchema(othersProp) >> "custom_schema"
+        mockCalculator.getJoinTableCatalog(othersProp) >> "custom_catalog"
 
         when:
         def collection = binder.bindCollection(othersProp, "")
 
         then:
         1 * mockCollector.addCollectionBinding(_)
-        collection.role == "${CBManyToManyA.name}.others".toString()
-        !collection.isOneToMany()
-        collection.collectionTable != null
-        collection.collectionTable.name == "cbmany_to_manya_others"
+        collection.collectionTable.name == "custom_join_table"
+        collection.collectionTable.schema == "custom_schema"
+        collection.collectionTable.catalog == "custom_catalog"
     }
 }
 
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/TableForManyCalculatorSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/TableForManyCalculatorSpec.groovy
index d02cbc58bf..bc64ebdb5a 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/TableForManyCalculatorSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/TableForManyCalculatorSpec.groovy
@@ -42,6 +42,9 @@ class TableForManyCalculatorSpec extends HibernateGormDatastoreSpec {
         def namingStrategy = getGrailsDomainBinder().getNamingStrategy()
         def backticksRemover = new BackticksRemover()
         def collector = Mock(InFlightMetadataCollector)
+        collector.addTable(_, _, _, _, _, _) >> { schema, catalog, name, sub, isAbstract, context ->
+            return new org.hibernate.mapping.Table("test", name)
+        }
 
         def calculator = new TableForManyCalculator(namingStrategy, collector, backticksRemover)
 
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyPropertySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyPropertySpec.groovy
index e62ab1ea88..4dd8f5437f 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyPropertySpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyPropertySpec.groovy
@@ -31,6 +31,11 @@ class HibernateManyToManyPropertySpec extends HibernateGormDatastoreSpec {
         given:
         def entityA = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(HMMPA.name)
         def property = (HibernateManyToManyProperty) entityA.getPropertyByName("others")
+        def mbc = getGrailsDomainBinder().metadataBuildingContext
+        def rootClass = new org.hibernate.mapping.RootClass(mbc)
+        rootClass.setEntityName(HMMPA.name)
+        def mockCollection = new org.hibernate.mapping.Set(mbc, rootClass)
+        property.setCollection(mockCollection, "")
 
         expect:
         property.getHibernateAssociatedEntity().name == HMMPB.name
@@ -40,6 +45,38 @@ class HibernateManyToManyPropertySpec extends HibernateGormDatastoreSpec {
         property.isLazy()
         !property.isAssociationColumnNullable()
     }
+
+    def "test getCollection throws exception if not initialized"() {
+        given:
+        def entityA = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(HMMPA.name)
+        def property = (HibernateManyToManyProperty) entityA.getPropertyByName("others")
+
+        when:
+        property.getCollection()
+
+        then:
+        def e = thrown(org.hibernate.MappingException)
+        e.message.contains("Hibernate Collection has not been initialized")
+    }
+
+    def "test setCollection with path configures metadata"() {
+        given:
+        def entityA = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(HMMPA.name)
+        def property = (HibernateManyToManyProperty) entityA.getPropertyByName("others")
+        def mbc = getGrailsDomainBinder().metadataBuildingContext
+        def rootClass = new org.hibernate.mapping.RootClass(mbc)
+        rootClass.setEntityName(HMMPA.name)
+        def mockCollection = new org.hibernate.mapping.Set(mbc, rootClass)
+
+        when:
+        property.setCollection(mockCollection, "foo.bar")
+
+        then:
+        property.getCollection() == mockCollection
+        mockCollection.getRole() == "${HMMPA.name}.foo.bar.others".toString()
+        mockCollection.getFetchMode() == property.getFetchMode()
+        mockCollection.getBatchSize() == property.getBatchSize()
+    }
 }
 
 @Entity