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 }
