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 3210b94c54d98c5db2a92757d2f78fd5899148ba
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Wed Feb 18 07:45:11 2026 -0600

    Refactor collection binding logic and align property hierarchy
    
    - Extract bindCollectionWithJoinTable into CollectionWithJoinTableBinder
    - Update CollectionForPropertyConfigBinder to accept 
HibernateToManyProperty directly
    - Modularize CollectionSecondPassBinder and CollectionBinder via 
constructor injection
    - Ensure HibernateBasicProperty and HibernateEmbeddedCollectionProperty 
implement HibernateToManyProperty for consistent treatment of all collections
---
 .../cfg/domainbinding/binder/CollectionBinder.java |  14 +-
 .../binder/CollectionForPropertyConfigBinder.java  |  23 ++--
 .../hibernate/HibernateBasicProperty.java          |   2 +-
 .../HibernateEmbeddedCollectionProperty.java       |   2 +-
 .../secondpass/CollectionSecondPassBinder.java     | 105 ++-------------
 .../secondpass/CollectionWithJoinTableBinder.java  | 144 +++++++++++++++++++++
 .../secondpass/ListSecondPassBinder.java           |   4 +-
 .../domainbinding/secondpass/MapSecondPass.java    |   2 +-
 .../secondpass/MapSecondPassBinder.java            |   4 +-
 .../CollectionForPropertyConfigBinderSpec.groovy   |  39 +-----
 .../CollectionWithJoinTableBinderSpec.groovy       | 101 +++++++++++++++
 11 files changed, 290 insertions(+), 150 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 16cecf44fc..aa045eb6e0 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
@@ -30,6 +30,7 @@ import 
org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolde
 import 
org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionType;
 import org.grails.orm.hibernate.cfg.domainbinding.util.GrailsPropertyResolver;
 
+import 
org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionWithJoinTableBinder;
 import 
org.grails.orm.hibernate.cfg.domainbinding.secondpass.UnidirectionalOneToManyInverseValuesBinder;
 import org.hibernate.FetchMode;
 import org.hibernate.boot.spi.InFlightMetadataCollector;
@@ -73,6 +74,8 @@ public class CollectionBinder {
         this.columnNameForPropertyAndPathFetcher = 
columnNameForPropertyAndPathFetcher;
         this.collectionHolder = collectionHolder;
         GrailsPropertyResolver grailsPropertyResolver = new 
GrailsPropertyResolver();
+        CollectionForPropertyConfigBinder collectionForPropertyConfigBinder = 
new CollectionForPropertyConfigBinder();
+        UnidirectionalOneToManyInverseValuesBinder 
unidirectionalOneToManyInverseValuesBinder = new 
UnidirectionalOneToManyInverseValuesBinder();
         this.collectionSecondPassBinder = new CollectionSecondPassBinder(
                 metadataBuildingContext,
                 namingStrategy,
@@ -87,7 +90,16 @@ public class CollectionBinder {
                 grailsPropertyResolver,
                 new BidirectionalOneToManyLinker(grailsPropertyResolver),
                 new DependentKeyValueBinder(simpleValueBinder, 
compositeIdentifierToManyToOneBinder),
-                new UnidirectionalOneToManyInverseValuesBinder()
+                unidirectionalOneToManyInverseValuesBinder,
+                new CollectionWithJoinTableBinder(
+                        metadataBuildingContext,
+                        namingStrategy,
+                        unidirectionalOneToManyInverseValuesBinder,
+                        enumTypeBinder,
+                        compositeIdentifierToManyToOneBinder,
+                        simpleValueColumnFetcher,
+                        collectionForPropertyConfigBinder
+                )
         );
         this.listSecondPassBinder = new 
ListSecondPassBinder(metadataBuildingContext, namingStrategy, 
collectionSecondPassBinder);
         this.mapSecondPassBinder = new 
MapSecondPassBinder(metadataBuildingContext, namingStrategy, 
collectionSecondPassBinder);
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java
index 77d666af75..bff134fcd5 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java
@@ -1,24 +1,17 @@
 package org.grails.orm.hibernate.cfg.domainbinding.binder;
 
-import org.grails.orm.hibernate.cfg.PropertyConfig;
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty;
 import org.hibernate.FetchMode;
 import org.hibernate.mapping.Collection;
 
+import java.util.Optional;
+
+import jakarta.annotation.Nonnull;
+
 public class CollectionForPropertyConfigBinder {
 
-    public void bindCollectionForPropertyConfig(Collection collection, 
PropertyConfig config) {
-        if (config == null) {
-            collection.setLazy(true);
-            collection.setExtraLazy(false);
-        } else {
-            final FetchMode fetch = config.getFetchMode();
-            if(!fetch.equals(FetchMode.JOIN)) {
-                collection.setLazy(true);
-            }
-            final Boolean lazy = config.getLazy();
-            if(lazy != null) {
-                collection.setExtraLazy(lazy);
-            }
-        }
+    public void bindCollectionForPropertyConfig(@Nonnull Collection 
collection,@Nonnull HibernateToManyProperty property) {
+        collection.setLazy(!FetchMode.JOIN.equals(property.getFetchMode()));
+        
Optional.ofNullable(property.getLazy()).ifPresent(collection::setExtraLazy);
     }
 }
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 36dde0aed1..8bfee6a6bb 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
@@ -11,7 +11,7 @@ import java.beans.PropertyDescriptor;
 /**
  * Hibernate implementation of {@link 
org.grails.datastore.mapping.model.types.Basic}
  */
-public class HibernateBasicProperty extends BasicWithMapping<PropertyConfig> 
implements GrailsHibernatePersistentProperty {
+public class HibernateBasicProperty extends BasicWithMapping<PropertyConfig> 
implements HibernateToManyProperty {
     public HibernateBasicProperty(GrailsHibernatePersistentEntity entity, 
MappingContext context, PropertyDescriptor property) {
         super(entity, context, property);
     }
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 e240bda39d..66e7a6c186 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
@@ -11,7 +11,7 @@ import java.beans.PropertyDescriptor;
 /**
  * Hibernate implementation of {@link 
org.grails.datastore.mapping.model.types.EmbeddedCollection}
  */
-public class HibernateEmbeddedCollectionProperty extends 
EmbeddedCollectionWithMapping<PropertyConfig> implements 
GrailsHibernatePersistentProperty {
+public class HibernateEmbeddedCollectionProperty extends 
EmbeddedCollectionWithMapping<PropertyConfig> implements 
HibernateToManyProperty {
     public HibernateEmbeddedCollectionProperty(PersistentEntity entity, 
MappingContext context, PropertyDescriptor property) {
         super(entity, context, property);
     }
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
index 1244486598..c326cd5690 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
@@ -63,6 +63,7 @@ public class CollectionSecondPassBinder {
     private final BidirectionalOneToManyLinker bidirectionalOneToManyLinker;
     private final DependentKeyValueBinder dependentKeyValueBinder;
     private final UnidirectionalOneToManyInverseValuesBinder 
unidirectionalOneToManyInverseValuesBinder;
+    private final CollectionWithJoinTableBinder collectionWithJoinTableBinder;
 
     public CollectionSecondPassBinder(
             MetadataBuildingContext metadataBuildingContext,
@@ -78,7 +79,8 @@ public class CollectionSecondPassBinder {
             GrailsPropertyResolver grailsPropertyResolver,
             BidirectionalOneToManyLinker bidirectionalOneToManyLinker,
             DependentKeyValueBinder dependentKeyValueBinder,
-            UnidirectionalOneToManyInverseValuesBinder 
unidirectionalOneToManyInverseValuesBinder) {
+            UnidirectionalOneToManyInverseValuesBinder 
unidirectionalOneToManyInverseValuesBinder,
+            CollectionWithJoinTableBinder collectionWithJoinTableBinder) {
         this.metadataBuildingContext = metadataBuildingContext;
         this.namingStrategy = namingStrategy;
         this.jdbcEnvironment = jdbcEnvironment;
@@ -93,14 +95,15 @@ public class CollectionSecondPassBinder {
         this.bidirectionalOneToManyLinker = bidirectionalOneToManyLinker;
         this.dependentKeyValueBinder = dependentKeyValueBinder;
         this.unidirectionalOneToManyInverseValuesBinder = 
unidirectionalOneToManyInverseValuesBinder;
+        this.collectionWithJoinTableBinder = collectionWithJoinTableBinder;
         this.defaultColumnNameFetcher = new 
DefaultColumnNameFetcher(namingStrategy);
         this.orderByClauseBuilder = new OrderByClauseBuilder();
     }
 
 
 
-    public void bindCollectionSecondPass(HibernateToManyProperty property, 
@Nonnull InFlightMetadataCollector mappings,
-                                         Map<?, ?> persistentClasses, 
Collection collection) {
+    public void bindCollectionSecondPass(@Nonnull HibernateToManyProperty 
property, @Nonnull InFlightMetadataCollector mappings,
+                                         Map<?, ?> persistentClasses, @Nonnull 
Collection collection) {
         PersistentClass associatedClass = null;
 
         if (LOG.isDebugEnabled())
@@ -157,7 +160,7 @@ public class CollectionSecondPassBinder {
                 collection.setCollectionTable(associatedClass.getTable());
             }
 
-            new 
CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, 
propConfig);
+            new 
CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, 
property);
         }
 
         final boolean isManyToMany = property instanceof 
HibernateManyToManyProperty;
@@ -227,7 +230,7 @@ public class CollectionSecondPassBinder {
                 ManyToOne element = 
manyToOneBinder.bindManyToOne((Association)otherSide, 
collection.getCollectionTable(), EMPTY_PATH);
                 
element.setReferencedEntityName(otherSide.getOwner().getName());
                 collection.setElement(element);
-                new 
CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, 
propConfig);
+                new 
CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, 
property);
                 if (property.isCircular()) {
                     collection.setInverse(false);
                 }
@@ -239,12 +242,12 @@ public class CollectionSecondPassBinder {
             // there are problems with list and map mappings and join columns 
relating to duplicate key constraints
             // TODO change this when HHH-1268 is resolved
             if (!property.shouldBindWithForeignKey()) {
-                bindCollectionWithJoinTable(property, mappings, collection, 
propConfig);
+                
collectionWithJoinTableBinder.bindCollectionWithJoinTable(property, mappings, 
collection);
             } else {
                 bindUnidirectionalOneToMany((HibernateOneToManyProperty) 
property, mappings, collection);
             }
         } else if (property.supportsJoinColumnMapping()) {
-            bindCollectionWithJoinTable(property, mappings, collection, 
propConfig);
+            
collectionWithJoinTableBinder.bindCollectionWithJoinTable(property, mappings, 
collection);
         }
         collectionKeyColumnUpdater.forceNullableAndCheckUpdatable(key, 
property); // Use the injected service
     }
@@ -276,95 +279,7 @@ public class CollectionSecondPassBinder {
         referenced.addProperty(prop);
     }
 
-    private void bindCollectionWithJoinTable(HibernateToManyProperty property,
-                                             @Nonnull 
InFlightMetadataCollector mappings, Collection collection, PropertyConfig 
config) {
 
-        collection.setInverse(false);
-        SimpleValue element;
-        final boolean isBasicCollectionType = property instanceof Basic;
-        if (isBasicCollectionType) {
-            element = new BasicValue(metadataBuildingContext, 
collection.getCollectionTable());
-        }
-        else {
-            // for a normal unidirectional one-to-many we use a join column
-            element = new ManyToOne(metadataBuildingContext, 
collection.getCollectionTable());
-            
unidirectionalOneToManyInverseValuesBinder.bindUnidirectionalOneToManyInverseValues(property,
 (ManyToOne) element);
-        }
-
-        String columnName;
-
-        var joinColumnMappingOptional = 
Optional.ofNullable(config).map(PropertyConfig::getJoinTableColumnConfig);
-        if (isBasicCollectionType) {
-            final Class<?> referencedType = property.getType();
-            String className = referencedType.getName();
-            final boolean isEnum = referencedType.isEnum();
-            if (joinColumnMappingOptional.isPresent()) {
-                columnName = joinColumnMappingOptional.get().getName();
-            }
-            else {
-                var clazz = namingStrategy.resolveColumnName(className);
-                var prop = namingStrategy.resolveTableName(property.getName());
-                columnName = isEnum ? clazz : new 
BackticksRemover().apply(prop) + UNDERSCORE + new 
BackticksRemover().apply(clazz);
-            }
-
-            if (isEnum) {
-                enumTypeBinder.bindEnumType(property, referencedType, element, 
columnName);
-            }
-            else {
-
-                Mapping mapping = null;
-                GrailsHibernatePersistentEntity domainClass = 
property.getHibernateOwner();
-                if (domainClass != null) {
-                    mapping = domainClass.getMappedForm();
-                }
-                String typeName = property.getTypeName();
-                if (typeName == null) {
-                    Type type = 
mappings.getTypeConfiguration().getBasicTypeRegistry().getRegisteredType(className);
-                    if (type != null) {
-                        typeName = type.getName();
-                    }
-                }
-                if (typeName == null) {
-                    String domainName = property.getHibernateOwner().getName();
-                    throw new MappingException("Missing type or column for 
column["+columnName+"] on domain["+domainName+"] referencing["+className+"]");
-                }
-
-                new SimpleValueColumnBinder().bindSimpleValue(element, 
typeName, columnName, true);
-                if (joinColumnMappingOptional.isPresent()) {
-                    Column column = 
simpleValueColumnFetcher.getColumnForSimpleValue(element);
-                    ColumnConfig columnConfig = 
joinColumnMappingOptional.get();
-                    final PropertyConfig mappedForm = property.getMappedForm();
-                    new 
ColumnConfigToColumnBinder().bindColumnConfigToColumn(column, columnConfig, 
mappedForm);
-                }
-            }
-        } else {
-            final GrailsHibernatePersistentEntity domainClass = 
property.getHibernateAssociatedEntity();
-
-            Mapping m = null;
-            if (domainClass != null) {
-                m = domainClass.getMappedForm();
-            }
-            if (m != null && m.hasCompositeIdentifier()) {
-                CompositeIdentity ci = (CompositeIdentity) m.getIdentity();
-                
compositeIdentifierToManyToOneBinder.bindCompositeIdentifierToManyToOne(property,
 element, ci, domainClass, EMPTY_PATH);
-            }
-            else {
-                if (joinColumnMappingOptional.isPresent()) {
-                    columnName = joinColumnMappingOptional.get().getName();
-                }
-                else {
-                    var decapitalize = 
domainClass.getHibernateRootEntity().getJavaClass().getSimpleName();
-                    columnName = 
namingStrategy.resolveColumnName(decapitalize) + FOREIGN_KEY_SUFFIX;
-                }
-
-                new SimpleValueColumnBinder().bindSimpleValue(element, "long", 
columnName, true);
-            }
-        }
-
-        collection.setElement(element);
-
-        new 
CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, 
config);
-    }
 
 
 
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinder.java
new file mode 100644
index 0000000000..ea8ca64583
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinder.java
@@ -0,0 +1,144 @@
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass;
+
+import jakarta.annotation.Nonnull;
+import org.grails.datastore.mapping.model.types.Basic;
+import org.grails.orm.hibernate.cfg.*;
+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.binder.CollectionForPropertyConfigBinder;
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.ColumnConfigToColumnBinder;
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder;
+import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder;
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder;
+import 
org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher;
+import org.hibernate.MappingException;
+import org.hibernate.boot.spi.InFlightMetadataCollector;
+import org.hibernate.boot.spi.MetadataBuildingContext;
+import org.hibernate.mapping.*;
+import org.hibernate.mapping.Collection;
+import org.hibernate.type.Type;
+
+import java.util.Optional;
+
+import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.*;
+
+/**
+ * Binds a collection with a join table.
+ */
+public class CollectionWithJoinTableBinder {
+
+    private final MetadataBuildingContext metadataBuildingContext;
+    private final PersistentEntityNamingStrategy namingStrategy;
+    private final UnidirectionalOneToManyInverseValuesBinder 
unidirectionalOneToManyInverseValuesBinder;
+    private final EnumTypeBinder enumTypeBinder;
+    private final CompositeIdentifierToManyToOneBinder 
compositeIdentifierToManyToOneBinder;
+    private final SimpleValueColumnFetcher simpleValueColumnFetcher;
+    private final CollectionForPropertyConfigBinder 
collectionForPropertyConfigBinder;
+
+    public CollectionWithJoinTableBinder(
+            MetadataBuildingContext metadataBuildingContext,
+            PersistentEntityNamingStrategy namingStrategy,
+            UnidirectionalOneToManyInverseValuesBinder 
unidirectionalOneToManyInverseValuesBinder,
+            EnumTypeBinder enumTypeBinder,
+            CompositeIdentifierToManyToOneBinder 
compositeIdentifierToManyToOneBinder,
+            SimpleValueColumnFetcher simpleValueColumnFetcher,
+            CollectionForPropertyConfigBinder 
collectionForPropertyConfigBinder) {
+        this.metadataBuildingContext = metadataBuildingContext;
+        this.namingStrategy = namingStrategy;
+        this.unidirectionalOneToManyInverseValuesBinder = 
unidirectionalOneToManyInverseValuesBinder;
+        this.enumTypeBinder = enumTypeBinder;
+        this.compositeIdentifierToManyToOneBinder = 
compositeIdentifierToManyToOneBinder;
+        this.simpleValueColumnFetcher = simpleValueColumnFetcher;
+        this.collectionForPropertyConfigBinder = 
collectionForPropertyConfigBinder;
+    }
+
+    public void bindCollectionWithJoinTable(@Nonnull HibernateToManyProperty 
property,
+                                             @Nonnull 
InFlightMetadataCollector mappings, @Nonnull Collection collection) {
+
+        collection.setInverse(false);
+        SimpleValue element;
+        final boolean isBasicCollectionType = property instanceof Basic;
+        if (isBasicCollectionType) {
+            element = new BasicValue(metadataBuildingContext, 
collection.getCollectionTable());
+        }
+        else {
+            // for a normal unidirectional one-to-many we use a join column
+            element = new ManyToOne(metadataBuildingContext, 
collection.getCollectionTable());
+            
unidirectionalOneToManyInverseValuesBinder.bindUnidirectionalOneToManyInverseValues(property,
 (ManyToOne) element);
+        }
+
+        String columnName;
+
+        var joinColumnMappingOptional = 
Optional.ofNullable(property.getMappedForm()).map(PropertyConfig::getJoinTableColumnConfig);
+        if (isBasicCollectionType) {
+            final Class<?> referencedType = ((Basic) 
property).getComponentType();
+            String className = referencedType.getName();
+            final boolean isEnum = referencedType.isEnum();
+            if (joinColumnMappingOptional.isPresent()) {
+                columnName = joinColumnMappingOptional.get().getName();
+            }
+            else {
+                var clazz = namingStrategy.resolveColumnName(className);
+                var prop = namingStrategy.resolveTableName(property.getName());
+                columnName = isEnum ? clazz : new 
BackticksRemover().apply(prop) + UNDERSCORE + new 
BackticksRemover().apply(clazz);
+            }
+
+            if (isEnum) {
+                enumTypeBinder.bindEnumType(property, referencedType, element, 
columnName);
+            }
+            else {
+
+                Mapping mapping = null;
+                GrailsHibernatePersistentEntity domainClass = 
property.getHibernateOwner();
+                if (domainClass != null) {
+                    mapping = domainClass.getMappedForm();
+                }
+                String typeName = property.getTypeName();
+                if (typeName == null) {
+                    Type type = 
mappings.getTypeConfiguration().getBasicTypeRegistry().getRegisteredType(className);
+                    if (type != null) {
+                        typeName = type.getName();
+                    }
+                }
+                if (typeName == null) {
+                    String domainName = property.getHibernateOwner().getName();
+                    throw new MappingException("Missing type or column for 
column["+columnName+"] on domain["+domainName+"] referencing["+className+"]");
+                }
+
+                new SimpleValueColumnBinder().bindSimpleValue(element, 
typeName, columnName, true);
+                if (joinColumnMappingOptional.isPresent()) {
+                    Column column = 
simpleValueColumnFetcher.getColumnForSimpleValue(element);
+                    ColumnConfig columnConfig = 
joinColumnMappingOptional.get();
+                    final PropertyConfig mappedForm = property.getMappedForm();
+                    new 
ColumnConfigToColumnBinder().bindColumnConfigToColumn(column, columnConfig, 
mappedForm);
+                }
+            }
+        } else {
+            final GrailsHibernatePersistentEntity domainClass = 
property.getHibernateAssociatedEntity();
+
+            Mapping m = null;
+            if (domainClass != null) {
+                m = domainClass.getMappedForm();
+            }
+            if (m != null && m.hasCompositeIdentifier()) {
+                CompositeIdentity ci = (CompositeIdentity) m.getIdentity();
+                
compositeIdentifierToManyToOneBinder.bindCompositeIdentifierToManyToOne(property,
 element, ci, domainClass, EMPTY_PATH);
+            }
+            else {
+                if (joinColumnMappingOptional.isPresent()) {
+                    columnName = joinColumnMappingOptional.get().getName();
+                }
+                else {
+                    var decapitalize = 
domainClass.getHibernateRootEntity().getJavaClass().getSimpleName();
+                    columnName = 
namingStrategy.resolveColumnName(decapitalize) + FOREIGN_KEY_SUFFIX;
+                }
+
+                new SimpleValueColumnBinder().bindSimpleValue(element, "long", 
columnName, true);
+            }
+        }
+
+        collection.setElement(element);
+
+        
collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, 
property);
+    }
+}
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java
index 85f5d08d33..a618bb87b6 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java
@@ -44,8 +44,8 @@ public class ListSecondPassBinder {
         this.namingStrategy = namingStrategy;
     }
 
-    public void bindListSecondPass(HibernateToManyProperty property, @Nonnull 
InFlightMetadataCollector mappings,
-                                   Map<?, ?> persistentClasses, List list) {
+    public void bindListSecondPass(@Nonnull HibernateToManyProperty property, 
@Nonnull InFlightMetadataCollector mappings,
+                                   Map<?, ?> persistentClasses, @Nonnull List 
list) {
 
         collectionSecondPassBinder.bindCollectionSecondPass(property, 
mappings, persistentClasses, list);
         String columnName = property.getIndexColumnName(namingStrategy);
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 7574c35a7f..610b8747b3 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
@@ -21,7 +21,7 @@ public class MapSecondPass implements 
org.hibernate.boot.spi.SecondPass, GrailsS
     protected final Collection collection;
 
     public MapSecondPass(MapSecondPassBinder mapSecondPassBinder, 
HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings,
-                         Collection coll) {
+                         @Nonnull Collection coll) {
         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/MapSecondPassBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java
index ab462f3242..1fe67ab891 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java
@@ -41,8 +41,8 @@ public class MapSecondPassBinder {
         this.collectionSecondPassBinder = collectionSecondPassBinder;
     }
 
-    public void bindMapSecondPass(HibernateToManyProperty property, @Nonnull 
InFlightMetadataCollector mappings,
-                                  Map<?, ?> persistentClasses, 
org.hibernate.mapping.Map map) {
+    public void bindMapSecondPass(@Nonnull HibernateToManyProperty property, 
@Nonnull InFlightMetadataCollector mappings,
+                                  Map<?, ?> persistentClasses, @Nonnull 
org.hibernate.mapping.Map map) {
         collectionSecondPassBinder.bindCollectionSecondPass(property, 
mappings, persistentClasses, map);
         SimpleValue value = new BasicValue(metadataBuildingContext, 
map.getCollectionTable());
 
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy
index 35cc9b0b84..ab3b095d53 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy
@@ -1,7 +1,7 @@
 package org.grails.orm.hibernate.cfg.domainbinding
 
 import grails.gorm.specs.HibernateGormDatastoreSpec
-import org.grails.orm.hibernate.cfg.PropertyConfig
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
 import org.hibernate.FetchMode
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.Set
@@ -17,40 +17,24 @@ class CollectionForPropertyConfigBinderSpec extends 
HibernateGormDatastoreSpec {
 
 
 
-    def "should apply default settings when the property config is null"() {
-        def grailsDomainBinder = getGrailsDomainBinder()
-        given: "A hibernate collection"
-        def owner = new RootClass(grailsDomainBinder.metadataBuildingContext)
-        def collection = new Set(grailsDomainBinder.metadataBuildingContext, 
owner)
-        // Set initial state to be different from the expected outcome
-        collection.setLazy(false)
-        collection.setExtraLazy(true)
-
-        when: "the binder is called with a null config"
-        binder.bindCollectionForPropertyConfig(collection, null)
-
-        then: "specific default values are applied"
-        collection.isLazy()
-        !collection.isExtraLazy()
-    }
 
     @Unroll
     def "should bind lazy settings based on fetch mode '#fetchMode.name()'} 
and an explicit lazy config of #lazySetting"() {
-        given: "A hibernate collection and a mocked property config"
+        given: "A hibernate collection and a mocked property"
         def owner = new RootClass(grailsDomainBinder.metadataBuildingContext)
         def collection = new Set(grailsDomainBinder.metadataBuildingContext, 
owner)
-        def config = Mock(PropertyConfig)
+        def property = Mock(HibernateToManyProperty)
 
         // Set initial state
         collection.setLazy(false)
         collection.setExtraLazy(false)
 
-        and: "the config is stubbed"
-        config.getFetchMode() >> fetchMode
-        config.getLazy() >> lazySetting
+        and: "the property is stubbed"
+        property.getFetchMode() >> fetchMode
+        property.getLazy() >> lazySetting
 
         when: "the binder is applied"
-        binder.bindCollectionForPropertyConfig(collection, config)
+        binder.bindCollectionForPropertyConfig(collection, property)
 
         then: "the collection's lazy and extraLazy properties are set 
according to the binder's logic"
         collection.isLazy() == expectedIsLazy
@@ -67,15 +51,6 @@ class CollectionForPropertyConfigBinderSpec extends 
HibernateGormDatastoreSpec {
 //        FetchMode.SUBSELECT | true      || true           | true
     }
 
-    def "should not throw an exception if the collection itself is null"() {
-        given: "A valid property config"
-        def config = Mock(PropertyConfig)
 
-        when: "the binder is called with a null collection"
-        binder.bindCollectionForPropertyConfig(null, config)
-
-        then: "a NullPointerException is thrown because the code does not 
guard against it"
-        thrown(NullPointerException)
-    }
 
 }
\ No newline at end of file
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinderSpec.groovy
new file mode 100644
index 0000000000..27ff1401ab
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinderSpec.groovy
@@ -0,0 +1,101 @@
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass
+
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
+import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.hibernate.mapping.Collection
+import org.hibernate.mapping.ManyToOne
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.Set
+import org.hibernate.mapping.Table
+import org.hibernate.type.spi.TypeConfiguration
+import spock.lang.Subject
+
+class CollectionWithJoinTableBinderSpec extends HibernateGormDatastoreSpec {
+
+    @Subject
+    CollectionWithJoinTableBinder binder
+
+    UnidirectionalOneToManyInverseValuesBinder 
unidirectionalOneToManyInverseValuesBinder = 
Mock(UnidirectionalOneToManyInverseValuesBinder)
+    EnumTypeBinder enumTypeBinder = Mock(EnumTypeBinder)
+    CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder 
= Mock(CompositeIdentifierToManyToOneBinder)
+    SimpleValueColumnFetcher simpleValueColumnFetcher = 
Mock(SimpleValueColumnFetcher)
+    CollectionForPropertyConfigBinder collectionForPropertyConfigBinder = 
Mock(CollectionForPropertyConfigBinder)
+
+    void setup() {
+        def domainBinder = getGrailsDomainBinder()
+        binder = new CollectionWithJoinTableBinder(
+                domainBinder.metadataBuildingContext,
+                domainBinder.namingStrategy,
+                unidirectionalOneToManyInverseValuesBinder,
+                enumTypeBinder,
+                compositeIdentifierToManyToOneBinder,
+                simpleValueColumnFetcher,
+                collectionForPropertyConfigBinder
+        )
+    }
+
+    void "test bindCollectionWithJoinTable for basic type"() {
+        given:
+        PersistentEntity authorEntity = createPersistentEntity(CWJTBAuthor)
+        HibernateToManyProperty property = (HibernateToManyProperty) 
authorEntity.getPropertyByName("tags")
+        def domainBinder = getGrailsDomainBinder()
+
+        InFlightMetadataCollector mappings = Mock(InFlightMetadataCollector)
+        mappings.getTypeConfiguration() >> new TypeConfiguration()
+
+        def owner = new RootClass(domainBinder.metadataBuildingContext)
+        Collection collection = new Set(domainBinder.metadataBuildingContext, 
owner)
+        collection.setCollectionTable(new Table("CWJTB_TAGS"))
+
+        when:
+        binder.bindCollectionWithJoinTable(property, mappings, collection)
+
+        then:
+        collection.getElement() != null
+        1 * 
collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, 
property)
+    }
+
+    void "test bindCollectionWithJoinTable for entity association"() {
+        given:
+        createPersistentEntity(CWJTBBook)
+        PersistentEntity authorEntity = createPersistentEntity(CWJTBAuthor)
+        HibernateToManyProperty property = (HibernateToManyProperty) 
authorEntity.getPropertyByName("books")
+        def domainBinder = getGrailsDomainBinder()
+
+        InFlightMetadataCollector mappings = Mock(InFlightMetadataCollector)
+
+        def owner = new RootClass(domainBinder.metadataBuildingContext)
+        Collection collection = new Set(domainBinder.metadataBuildingContext, 
owner)
+        collection.setCollectionTable(new Table("CWJTB_BOOKS"))
+
+        when:
+        binder.bindCollectionWithJoinTable(property, mappings, collection)
+
+        then:
+        collection.getElement() instanceof ManyToOne
+        1 * 
collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, 
property)
+    }
+}
+
+@Entity
+class CWJTBBook {
+    Long id
+    String title
+}
+
+@Entity
+class CWJTBAuthor {
+    Long id
+    String name
+    java.util.Set<CWJTBBook> books
+    java.util.Set<String> tags
+    static hasMany = [books: CWJTBBook, tags: String]
+}


Reply via email to