This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 380769aaf63cc776225cfee7d8f878f473101715 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Tue Mar 3 11:06:36 2026 -0600 refactor: extract bindBidirectionalMapElement into BidirectionalMapElementBinder --- .../cfg/domainbinding/binder/CollectionBinder.java | 2 + .../secondpass/BidirectionalMapElementBinder.java | 54 ++++++++++++++ .../secondpass/CollectionSecondPassBinder.java | 17 ++--- .../BidirectionalMapElementBinderSpec.groovy | 82 ++++++++++++++++++++++ .../CollectionSecondPassBinderSpec.groovy | 2 +- 5 files changed, 143 insertions(+), 14 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 f8ec48d3fd..0ddbbfb8f8 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.hibernate.HibernatePersistentP import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.BidirectionalOneToManyLinker; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionKeyColumnUpdater; +import org.grails.orm.hibernate.cfg.domainbinding.secondpass.BidirectionalMapElementBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionKeyBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionMultiTenantFilterBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionOrderByBinder; @@ -118,6 +119,7 @@ public class CollectionBinder { new BidirectionalOneToManyLinker(grailsPropertyResolver), new DependentKeyValueBinder(simpleValueBinder, compositeIdentifierToManyToOneBinder), simpleValueColumnBinder), + new BidirectionalMapElementBinder(manyToOneBinder, collectionForPropertyConfigBinder), new CollectionOrderByBinder(), new CollectionMultiTenantFilterBinder(new DefaultColumnNameFetcher(namingStrategy))); this.listSecondPassBinder = diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BidirectionalMapElementBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BidirectionalMapElementBinder.java new file mode 100644 index 0000000000..a16946ccb0 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BidirectionalMapElementBinder.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.orm.hibernate.cfg.domainbinding.secondpass; + +import static org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsDomainBinder.EMPTY_PATH; + +import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder; +import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToOneProperty; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.ManyToOne; + +/** Binds the element of a bidirectional one-to-many Map association. */ +public class BidirectionalMapElementBinder { + + private final ManyToOneBinder manyToOneBinder; + private final CollectionForPropertyConfigBinder collectionForPropertyConfigBinder; + + /** Creates a new {@link BidirectionalMapElementBinder} instance. */ + public BidirectionalMapElementBinder( + ManyToOneBinder manyToOneBinder, + CollectionForPropertyConfigBinder collectionForPropertyConfigBinder) { + this.manyToOneBinder = manyToOneBinder; + this.collectionForPropertyConfigBinder = collectionForPropertyConfigBinder; + } + + /** Binds the ManyToOne element for a bidirectional Map collection. */ + public void bind(HibernateToManyProperty property, Collection collection) { + HibernateManyToOneProperty otherSide = + (HibernateManyToOneProperty) property.getHibernateInverseSide(); + ManyToOne element = + manyToOneBinder.bindManyToOne(otherSide, collection.getCollectionTable(), EMPTY_PATH); + element.setReferencedEntityName(otherSide.getOwner().getName()); + collection.setElement(element); + collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, 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 c413e9eeb2..ee69807823 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 @@ -28,7 +28,6 @@ import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; -import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToOneProperty; import org.hibernate.MappingException; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.mapping.*; @@ -40,6 +39,7 @@ public class CollectionSecondPassBinder { private final CollectionOrderByBinder collectionOrderByBinder; private final CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder; private final CollectionKeyBinder collectionKeyBinder; + private final BidirectionalMapElementBinder bidirectionalMapElementBinder; private final ManyToOneBinder manyToOneBinder; private final PrimaryKeyValueCreator primaryKeyValueCreator; private final CollectionKeyColumnUpdater collectionKeyColumnUpdater; @@ -56,6 +56,7 @@ public class CollectionSecondPassBinder { CollectionWithJoinTableBinder collectionWithJoinTableBinder, CollectionForPropertyConfigBinder collectionForPropertyConfigBinder, CollectionKeyBinder collectionKeyBinder, + BidirectionalMapElementBinder bidirectionalMapElementBinder, CollectionOrderByBinder collectionOrderByBinder, CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder) { this.manyToOneBinder = manyToOneBinder; @@ -65,6 +66,7 @@ public class CollectionSecondPassBinder { this.collectionWithJoinTableBinder = collectionWithJoinTableBinder; this.collectionForPropertyConfigBinder = collectionForPropertyConfigBinder; this.collectionKeyBinder = collectionKeyBinder; + this.bidirectionalMapElementBinder = bidirectionalMapElementBinder; this.collectionOrderByBinder = collectionOrderByBinder; this.collectionMultiTenantFilterBinder = collectionMultiTenantFilterBinder; } @@ -119,7 +121,7 @@ public class CollectionSecondPassBinder { if (property instanceof HibernateManyToManyProperty manyToMany && manyToMany.isBidirectional()) { bindManyToManyElement(manyToMany, collection); } else if (property.isBidirectionalOneToManyMap() && property.isBidirectional()) { - bindBidirectionalMapElement(property, collection); + bidirectionalMapElementBinder.bind(property, collection); } else if (property instanceof HibernateOneToManyProperty oneToManyProperty && oneToManyProperty.isUnidirectionalOneToMany()) { unidirectionalOneToManyBinder.bind(oneToManyProperty, collection); } else if (property.supportsJoinColumnMapping()) { @@ -140,17 +142,6 @@ public class CollectionSecondPassBinder { } } - private void bindBidirectionalMapElement( - HibernateToManyProperty property, Collection collection) { - HibernateManyToOneProperty otherSide = - (HibernateManyToOneProperty) property.getHibernateInverseSide(); - ManyToOne element = - manyToOneBinder.bindManyToOne(otherSide, collection.getCollectionTable(), EMPTY_PATH); - element.setReferencedEntityName(otherSide.getOwner().getName()); - collection.setElement(element); - collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, property); - } - private @Nonnull PersistentClass resolveAssociatedClass( HibernateToManyProperty property, Map<?, ?> persistentClasses) { return Optional.ofNullable(property.getHibernateAssociatedEntity()) diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BidirectionalMapElementBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BidirectionalMapElementBinderSpec.groovy new file mode 100644 index 0000000000..409701a4b3 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BidirectionalMapElementBinderSpec.groovy @@ -0,0 +1,82 @@ +package org.grails.orm.hibernate.cfg.domainbinding.secondpass + +import grails.gorm.annotation.Entity +import grails.gorm.specs.HibernateGormDatastoreSpec +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.ManyToOneBinder +import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder +import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty +import org.hibernate.mapping.Bag +import org.hibernate.mapping.ManyToOne +import org.hibernate.mapping.Table +import spock.lang.Subject + +class BidirectionalMapElementBinderSpec extends HibernateGormDatastoreSpec { + + @Subject + BidirectionalMapElementBinder binder + + void setupSpec() { + manager.addAllDomainClasses([ + BBMEOwner, + BBMEItem, + ]) + } + + void setup() { + def gdb = getGrailsDomainBinder() + def mbc = gdb.getMetadataBuildingContext() + def ns = gdb.getNamingStrategy() + def je = gdb.getJdbcEnvironment() + def svb = new SimpleValueBinder(mbc, ns, je) + def citmto = new CompositeIdentifierToManyToOneBinder(mbc, ns, je) + def mtob = new ManyToOneBinder(mbc, ns, svb, new ManyToOneValuesBinder(), citmto) + binder = new BidirectionalMapElementBinder(mtob, new CollectionForPropertyConfigBinder()) + } + + private HibernateToManyProperty propertyFor(Class ownerClass, String name = "items") { + (getPersistentEntity(ownerClass) as GrailsHibernatePersistentEntity).getPropertyByName(name) as HibernateToManyProperty + } + + def "bind sets ManyToOne element referencing the inverse side's owner"() { + given: + def property = propertyFor(BBMEOwner) + def mbc = getGrailsDomainBinder().getMetadataBuildingContext() + def collection = new Bag(mbc, null) + collection.setCollectionTable(new Table("test", "bbme_owner_items")) + + when: + binder.bind(property, collection) + + then: + collection.getElement() instanceof ManyToOne + (collection.getElement() as ManyToOne).getReferencedEntityName() == BBMEItem.name + } + + def "bind honours isBidirectionalOneToManyMap on the property"() { + given: + def property = propertyFor(BBMEOwner) + + expect: + property.isBidirectionalOneToManyMap() + property.isBidirectional() + } +} + +@Entity +class BBMEOwner { + Long id + Map<String, BBMEItem> items + static hasMany = [items: BBMEItem] +} + +@Entity +class BBMEItem { + Long id + String description + BBMEOwner owner + static belongsTo = [owner: BBMEOwner] +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy index 2f01558daf..49f127415d 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy @@ -41,7 +41,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec { def dcnf = new DefaultColumnNameFetcher(ns, new BackticksRemover()) def svcb = new SimpleValueColumnBinder() - binder = new CollectionSecondPassBinder(mtob, pkvc, cku, uotmb, cwjtb, cfpcb, new CollectionKeyBinder(botml, dkvb, svcb), new CollectionOrderByBinder(), new CollectionMultiTenantFilterBinder(dcnf)) + binder = new CollectionSecondPassBinder(mtob, pkvc, cku, uotmb, cwjtb, cfpcb, new CollectionKeyBinder(botml, dkvb, svcb), new BidirectionalMapElementBinder(mtob, cfpcb), new CollectionOrderByBinder(), new CollectionMultiTenantFilterBinder(dcnf)) } protected HibernatePersistentProperty createTestHibernateToManyProperty(Class<?> domainClass = CSPBTestEntityWithMany, String propertyName = "items") {
