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/02/17 18:42:10 UTC
(grails-core) 11/26: Refine ComponentBinder association logic and enhance its unit tests
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 <wb...@mac.com>
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 }