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 406d3480c69cd421a336c959390aebaa5fb65c8c
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Mon Feb 16 09:43:40 2026 -0600

    Refine ComponentBinder association logic and enhance its unit tests
---
 .../cfg/domainbinding/binder/ComponentBinder.java  |  25 ++-
 .../cfg/domainbinding/ComponentBinderSpec.groovy   | 226 ++++++++++++++++++++-
 2 files changed, 233 insertions(+), 18 deletions(-)

diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ComponentBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ComponentBinder.java
index d9044f5ef0..a565011b72 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ComponentBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ComponentBinder.java
@@ -21,6 +21,9 @@ import 
org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty;
 import org.grails.orm.hibernate.cfg.GrailsHibernateUtil;
 import org.grails.orm.hibernate.cfg.MappingCacheHolder;
 import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateEmbeddedProperty;
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToOneProperty;
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty;
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToOneProperty;
 import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty;
 import 
org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder;
 import 
org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionType;
@@ -108,6 +111,7 @@ public class ComponentBinder {
         // see if it's a collection type
         CollectionType collectionType = 
collectionHolder.get(currentGrailsProp.getType());
         if (collectionType != null) {
+            //HibernateToManyProperty
             // create collection
             Collection collection = 
collectionType.create((HibernateToManyProperty) currentGrailsProp, 
persistentClass);
             collectionBinder.bindCollection((HibernateToManyProperty) 
currentGrailsProp, collection, persistentClass, mappings, path);
@@ -115,28 +119,27 @@ public class ComponentBinder {
             value = collection;
         }
         // work out what type of relationship it is and bind value
-        else if (currentGrailsProp instanceof 
org.grails.datastore.mapping.model.types.ManyToOne) {
+        else if (currentGrailsProp.isHibernateOneToOne()) {
+            //HibernateOneToOneProperty
             if (LOG.isDebugEnabled())
-                LOG.debug("[GrailsDomainBinder] Binding property [" + 
currentGrailsProp.getName() + "] as ManyToOne");
+                LOG.debug("[GrailsDomainBinder] Binding property [" + 
currentGrailsProp.getName() + "] as OneToOne");
 
-            value = manyToOneBinder.bindManyToOne((Association) 
currentGrailsProp, table, path);
-        } else if (currentGrailsProp instanceof 
org.grails.datastore.mapping.model.types.OneToOne association) {
+            value = 
oneToOneBinder.bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) 
currentGrailsProp, persistentClass, table, path);
+        } else if (currentGrailsProp.isHibernateManyToOne()) {
+            //HibernateManyToOneProperty
             if (LOG.isDebugEnabled())
-                LOG.debug("[GrailsDomainBinder] Binding property [" + 
currentGrailsProp.getName() + "] as OneToOne");
+                LOG.debug("[GrailsDomainBinder] Binding property [" + 
currentGrailsProp.getName() + "] as ManyToOne");
 
-            if (association.canBindOneToOneWithSingleColumnAndForeignKey()) {
-                value = 
oneToOneBinder.bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) 
currentGrailsProp, persistentClass, table, path);
-            }
-            else {
-                value = manyToOneBinder.bindManyToOne((Association) 
currentGrailsProp, table, path);
-            }
+            value = manyToOneBinder.bindManyToOne((Association) 
currentGrailsProp, table, path);
         }
         else if (currentGrailsProp instanceof HibernateEmbeddedProperty 
embedded) {
             value = bindComponent(persistentClass, embedded, mappings);
         }
         else  if (currentGrailsProp.getType().isEnum()) {
+            //HibernateEnumTypeProperty
             value = enumTypeBinder.bindEnumType(currentGrailsProp, 
currentGrailsProp.getType(), table, path);
         }  else {
+            //HibernateSimpleProperty
                 value = new BasicValue(metadataBuildingContext, table);
                 this.simpleValueBinder.bindSimpleValue(currentGrailsProp, 
componentProperty, (SimpleValue) value, path);
         }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinderSpec.groovy
index 2c96f3bff2..f89e4dacdc 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinderSpec.groovy
@@ -11,9 +11,11 @@ import org.grails.orm.hibernate.cfg.Mapping
 import org.grails.orm.hibernate.cfg.MappingCacheHolder
 import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy
 import org.grails.orm.hibernate.cfg.PropertyConfig
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateBasicProperty
 import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateEmbeddedProperty
 import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToOneProperty
 import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToOneProperty
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateSimpleProperty
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.ComponentUpdater
@@ -24,6 +26,7 @@ import 
org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
 import 
org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher
 import 
org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder
 import org.hibernate.boot.spi.InFlightMetadataCollector
+import org.hibernate.boot.spi.MetadataBuildingContext
 import org.hibernate.mapping.BasicValue
 import org.hibernate.mapping.Component
 import org.hibernate.mapping.ManyToOne as HibernateManyToOne
@@ -50,8 +53,32 @@ class ComponentBinderSpec extends HibernateGormDatastoreSpec 
{
         }
     }
 
+    abstract static class TestOneToMany extends 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty 
{
+        TestOneToMany(PersistentEntity owner, MappingContext context, 
java.beans.PropertyDescriptor descriptor) {
+            super(owner, context, descriptor);
+        }
+    }
+
+    abstract static class TestEmbedded extends HibernateEmbeddedProperty {
+        TestEmbedded(PersistentEntity owner, MappingContext context, 
java.beans.PropertyDescriptor descriptor) {
+            super(owner, context, descriptor);
+        }
+    }
+
+    abstract static class TestBasic extends HibernateBasicProperty {
+        TestBasic(GrailsHibernatePersistentEntity owner, MappingContext 
context, java.beans.PropertyDescriptor descriptor) {
+            super(owner, context, descriptor);
+        }
+    }
+
+    abstract static class TestSimple extends HibernateSimpleProperty {
+        TestSimple(PersistentEntity owner, MappingContext context, 
java.beans.PropertyDescriptor descriptor) {
+            super(owner, context, descriptor);
+        }
+    }
+
     MappingCacheHolder mappingCacheHolder = Mock(MappingCacheHolder)
-    CollectionHolder collectionHolder = new CollectionHolder([:])
+    CollectionHolder collectionHolder
     EnumTypeBinder enumTypeBinder = Mock(EnumTypeBinder)
     CollectionBinder collectionBinder = Mock(CollectionBinder)
     PropertyFromValueCreator propertyFromValueCreator = 
Mock(PropertyFromValueCreator)
@@ -65,8 +92,10 @@ class ComponentBinderSpec extends HibernateGormDatastoreSpec 
{
     ComponentBinder binder
 
     def setup() {
+        def metadataBuildingContext = 
getGrailsDomainBinder().getMetadataBuildingContext()
+        collectionHolder = new CollectionHolder(metadataBuildingContext)
         binder = new ComponentBinder(
-                getGrailsDomainBinder().getMetadataBuildingContext(),
+                metadataBuildingContext,
                 mappingCacheHolder,
                 collectionHolder,
                 enumTypeBinder,
@@ -97,7 +126,7 @@ class ComponentBinderSpec extends HibernateGormDatastoreSpec 
{
         root.setEntityName("MyEntity")
         root.setTable(new Table("my_entity"))
         
-        def embeddedProp = GroovyMock(HibernateEmbeddedProperty)
+        def embeddedProp = Mock(TestEmbedded)
         def associatedEntity = GroovyMock(GrailsHibernatePersistentEntity)
         
         embeddedProp.getType() >> Address
@@ -106,11 +135,15 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         embeddedProp.getOwner() >> Mock(GrailsHibernatePersistentEntity) {
             getJavaClass() >> MyEntity
         }
+        embeddedProp.isHibernateOneToOne() >> false
+        embeddedProp.isHibernateManyToOne() >> false
 
         associatedEntity.getName() >> "Address"
-        def prop1 = Mock(GrailsHibernatePersistentProperty)
+        def prop1 = Mock(TestSimple)
         prop1.getName() >> "street"
         prop1.getType() >> String
+        prop1.isHibernateOneToOne() >> false
+        prop1.isHibernateManyToOne() >> false
         associatedEntity.getHibernatePersistentProperties() >> [prop1]
         associatedEntity.getIdentity() >> null
 
@@ -127,6 +160,87 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         1 * componentUpdater.updateComponent(_ as Component, embeddedProp, 
prop1, _ as Value)
     }
 
+    def "should skip identity and version properties"() {
+        given:
+        def metadataBuildingContext = 
getGrailsDomainBinder().getMetadataBuildingContext()
+        def root = new RootClass(metadataBuildingContext)
+        root.setEntityName("MyEntity")
+        root.setTable(new Table("my_entity"))
+
+        def embeddedProp = Mock(TestEmbedded)
+        def associatedEntity = GroovyMock(GrailsHibernatePersistentEntity)
+
+        embeddedProp.getType() >> Address
+        embeddedProp.getName() >> "address"
+        embeddedProp.getAssociatedEntity() >> associatedEntity
+        embeddedProp.getOwner() >> Mock(GrailsHibernatePersistentEntity) {
+            getJavaClass() >> MyEntity
+        }
+        embeddedProp.isHibernateOneToOne() >> false
+        embeddedProp.isHibernateManyToOne() >> false
+
+        associatedEntity.getName() >> "Address"
+        def idProp = 
Mock(org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateIdentityProperty)
+        idProp.getName() >> "id"
+        def versionProp = Mock(TestSimple)
+        versionProp.getName() >> "version"
+        def normalProp = Mock(TestSimple)
+        normalProp.getName() >> "street"
+        normalProp.getType() >> String
+        normalProp.isHibernateOneToOne() >> false
+        normalProp.isHibernateManyToOne() >> false
+
+        associatedEntity.getIdentity() >> idProp
+        associatedEntity.getHibernatePersistentProperties() >> [idProp, 
versionProp, normalProp]
+
+        def mappings = metadataBuildingContext.getMetadataCollector()
+
+        when:
+        binder.bindComponent(root, embeddedProp, mappings)
+
+        then:
+        0 * componentUpdater.updateComponent(_, _, idProp, _)
+        0 * componentUpdater.updateComponent(_, _, versionProp, _)
+        1 * componentUpdater.updateComponent(_, _, normalProp, _)
+    }
+
+    def "should set parent property when component has reference back to 
owner"() {
+        given:
+        def metadataBuildingContext = 
getGrailsDomainBinder().getMetadataBuildingContext()
+        def root = new RootClass(metadataBuildingContext)
+        root.setEntityName("MyEntity")
+        root.setTable(new Table("my_entity"))
+
+        def embeddedProp = Mock(TestEmbedded)
+        def associatedEntity = GroovyMock(GrailsHibernatePersistentEntity)
+
+        embeddedProp.getType() >> Address
+        embeddedProp.getName() >> "address"
+        embeddedProp.getAssociatedEntity() >> associatedEntity
+        embeddedProp.getOwner() >> Mock(GrailsHibernatePersistentEntity) {
+            getJavaClass() >> MyEntity
+        }
+        embeddedProp.isHibernateOneToOne() >> false
+        embeddedProp.isHibernateManyToOne() >> false
+
+        associatedEntity.getName() >> "Address"
+        def parentProp = Mock(TestSimple)
+        parentProp.getName() >> "myEntity"
+        parentProp.getType() >> MyEntity
+
+        associatedEntity.getIdentity() >> null
+        associatedEntity.getHibernatePersistentProperties() >> [parentProp]
+
+        def mappings = metadataBuildingContext.getMetadataCollector()
+
+        when:
+        def component = binder.bindComponent(root, embeddedProp, mappings)
+
+        then:
+        component.getParentProperty() == "myEntity"
+        0 * componentUpdater.updateComponent(_, _, parentProp, _)
+    }
+
     def "should bind simple property"() {
         given:
         def metadataBuildingContext = 
getGrailsDomainBinder().getMetadataBuildingContext()
@@ -136,7 +250,7 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         def ownerEntity = Mock(GrailsHibernatePersistentEntity)
         ownerEntity.isRoot() >> true
 
-        def currentGrailsProp = Mock(GrailsHibernatePersistentProperty)
+        def currentGrailsProp = Mock(TestSimple)
         
         def componentProperty = Mock(GrailsHibernatePersistentProperty)
         def mappings = Mock(InFlightMetadataCollector)
@@ -146,6 +260,8 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         def mapping = new Mapping()
         ownerEntity.getMappedForm() >> mapping
         currentGrailsProp.getType() >> String
+        currentGrailsProp.isHibernateOneToOne() >> false
+        currentGrailsProp.isHibernateManyToOne() >> false
         setupProperty(currentGrailsProp, "street", mapping, ownerEntity)
         setupProperty(componentProperty, "address", mapping, ownerEntity)
         
@@ -180,6 +296,8 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
             isRoot() >> true
         }
         currentGrailsProp.getType() >> Object
+        currentGrailsProp.isHibernateOneToOne() >> false
+        currentGrailsProp.isHibernateManyToOne() >> true
         setupProperty(currentGrailsProp, "owner", mapping, ownerEntity)
         setupProperty(componentProperty, "address", mapping, ownerEntity)
         
@@ -217,6 +335,7 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         }
         currentGrailsProp.getType() >> Object
         
((Association)currentGrailsProp).canBindOneToOneWithSingleColumnAndForeignKey() 
>> true
+        currentGrailsProp.isHibernateOneToOne() >> true
         setupProperty(currentGrailsProp, "detail", mapping, ownerEntity)
         setupProperty(componentProperty, "address", mapping, ownerEntity)
         def hibernateOneToOne = new HibernateOneToOne(metadataBuildingContext, 
table, root)
@@ -237,7 +356,7 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         def table = new Table("my_table")
         def ownerEntity = Mock(GrailsHibernatePersistentEntity)
         ownerEntity.isRoot() >> true
-        def currentGrailsProp = Mock(GrailsHibernatePersistentProperty)
+        def currentGrailsProp = Mock(TestBasic)
         def componentProperty = Mock(GrailsHibernatePersistentProperty)
         def mappings = Mock(InFlightMetadataCollector)
         def hibernateProperty = new Property()
@@ -246,6 +365,8 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         def mapping = new Mapping()
         ownerEntity.getMappedForm() >> mapping
         currentGrailsProp.getType() >> MyEnum
+        currentGrailsProp.isHibernateOneToOne() >> false
+        currentGrailsProp.isHibernateManyToOne() >> false
         setupProperty(currentGrailsProp, "type", mapping, ownerEntity)
         setupProperty(componentProperty, "address", mapping, ownerEntity)
         
@@ -267,7 +388,7 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         def table = new Table("my_table")
         def ownerEntity = Mock(GrailsHibernatePersistentEntity)
         ownerEntity.isRoot() >> true
-        def currentGrailsProp = Mock(GrailsHibernatePersistentProperty)
+        def currentGrailsProp = Mock(TestSimple)
         def componentProperty = Mock(GrailsHibernatePersistentProperty)
         def mappings = Mock(InFlightMetadataCollector)
         def hibernateProperty = new Property()
@@ -276,6 +397,8 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         def mapping = new Mapping()
         ownerEntity.getMappedForm() >> mapping
         currentGrailsProp.getType() >> String
+        currentGrailsProp.isHibernateOneToOne() >> false
+        currentGrailsProp.isHibernateManyToOne() >> false
         setupProperty(currentGrailsProp, "street", mapping, ownerEntity)
         setupProperty(componentProperty, "address", mapping, ownerEntity)
         
@@ -294,6 +417,95 @@ class ComponentBinderSpec extends 
HibernateGormDatastoreSpec {
         0 * componentUpdater.updateComponent(_, _, _, _)
     }
 
+    def "should bind collection property within component"() {
+        given:
+        def metadataBuildingContext = 
getGrailsDomainBinder().getMetadataBuildingContext()
+        def root = new RootClass(metadataBuildingContext)
+        def component = new Component(metadataBuildingContext, root)
+        def table = new Table("my_table")
+        def currentGrailsProp = 
Mock(org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty)
+        def componentProperty = Mock(GrailsHibernatePersistentProperty)
+        def mappings = Mock(InFlightMetadataCollector)
+        
+        currentGrailsProp.getType() >> Set
+        currentGrailsProp.isHibernateOneToOne() >> false
+        currentGrailsProp.isHibernateManyToOne() >> false
+
+        when:
+        def result = binder.bindComponentProperty(component, 
componentProperty, currentGrailsProp, root, "address", table, mappings)
+
+        then:
+        result instanceof org.hibernate.mapping.Set
+        1 * collectionBinder.bindCollection(currentGrailsProp, _ as 
org.hibernate.mapping.Set, root, mappings, "address")
+        1 * mappings.addCollectionBinding(_ as org.hibernate.mapping.Set)
+    }
+
+    def "should bind nested component recursively"() {
+        given:
+        def metadataBuildingContext = 
getGrailsDomainBinder().getMetadataBuildingContext()
+        def root = new RootClass(metadataBuildingContext)
+        root.setTable(new Table("my_table"))
+        def component = new Component(metadataBuildingContext, root)
+        def table = root.getTable()
+        def mappings = Mock(InFlightMetadataCollector)
+        
+        def nestedEmbeddedProp = Mock(TestEmbedded)
+        def nestedAssociatedEntity = Mock(GrailsHibernatePersistentEntity)
+        def parentProp = Mock(GrailsHibernatePersistentProperty)
+        
+        nestedEmbeddedProp.getType() >> Address
+        nestedEmbeddedProp.getName() >> "nestedAddress"
+        nestedEmbeddedProp.getAssociatedEntity() >> nestedAssociatedEntity
+        nestedEmbeddedProp.getOwner() >> Mock(GrailsHibernatePersistentEntity) 
{
+            getJavaClass() >> Address
+        }
+        nestedEmbeddedProp.isHibernateOneToOne() >> false
+        nestedEmbeddedProp.isHibernateManyToOne() >> false
+        
+        nestedAssociatedEntity.getName() >> "NestedAddress"
+        def nestedProp1 = Mock(TestSimple)
+        nestedProp1.getName() >> "street"
+        nestedProp1.getType() >> String
+        nestedProp1.isHibernateOneToOne() >> false
+        nestedProp1.isHibernateManyToOne() >> false
+        nestedAssociatedEntity.getHibernatePersistentProperties() >> 
[nestedProp1]
+        nestedAssociatedEntity.getIdentity() >> null
+
+        when:
+        def result = binder.bindComponentProperty(component, parentProp, 
nestedEmbeddedProp, root, "address.nested", table, mappings)
+
+        then:
+        result instanceof Component
+        result.getComponentClassName() == Address.name
+        1 * mappingCacheHolder.cacheMapping(nestedAssociatedEntity)
+        1 * mockSimpleValueBinder.bindSimpleValue(nestedProp1, 
nestedEmbeddedProp, _ as BasicValue, "nestedAddress")
+        1 * componentUpdater.updateComponent(_ as Component, 
nestedEmbeddedProp, nestedProp1, _ as Value)
+    }
+
+    def "should bind one-to-one as many-to-one when it cannot be bound with 
single column"() {
+        given:
+        def metadataBuildingContext = 
getGrailsDomainBinder().getMetadataBuildingContext()
+        def root = new RootClass(metadataBuildingContext)
+        def component = new Component(metadataBuildingContext, root)
+        def table = new Table("my_table")
+        def currentGrailsProp = Mock(TestOneToOne)
+        def componentProperty = Mock(GrailsHibernatePersistentProperty)
+        def mappings = Mock(InFlightMetadataCollector)
+        
+        currentGrailsProp.canBindOneToOneWithSingleColumnAndForeignKey() >> 
false
+        currentGrailsProp.getType() >> Object
+        currentGrailsProp.isHibernateOneToOne() >> false
+        currentGrailsProp.isHibernateManyToOne() >> true
+        def hibernateManyToOne = new 
HibernateManyToOne(metadataBuildingContext, table)
+
+        when:
+        def result = binder.bindComponentProperty(component, 
componentProperty, currentGrailsProp, root, "address", table, mappings)
+
+        then:
+        1 * manyToOneBinder.bindManyToOne(currentGrailsProp, table, "address") 
>> hibernateManyToOne
+        result == hibernateManyToOne
+    }
+
     static class MyEntity {}
     static class Address {}
     enum MyEnum { VAL }

Reply via email to