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 6cf5923907b2dc2da036679aab3b791a42c7aaa4 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Tue Mar 3 11:57:26 2026 -0600 refactor: extract bindManyToManyElement into ManyToManyElementBinder --- .../cfg/domainbinding/binder/CollectionBinder.java | 3 +- .../secondpass/CollectionSecondPassBinder.java | 24 +----- .../secondpass/ManyToManyElementBinder.java | 55 ++++++++++++ .../CollectionSecondPassBinderSpec.groovy | 2 +- .../secondpass/ManyToManyElementBinderSpec.groovy | 99 ++++++++++++++++++++++ 5 files changed, 161 insertions(+), 22 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 0ddbbfb8f8..2a9fa23bcc 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 @@ -32,6 +32,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.secondpass.BidirectionalOneToM 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.ManyToManyElementBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionMultiTenantFilterBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionOrderByBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPassBinder; @@ -109,7 +110,6 @@ public class CollectionBinder { new ColumnConfigToColumnBinder()); this.collectionSecondPassBinder = new CollectionSecondPassBinder( - manyToOneBinder, new PrimaryKeyValueCreator(metadataBuildingContext), new CollectionKeyColumnUpdater(), new UnidirectionalOneToManyBinder(collectionWithJoinTableBinder, mappings), @@ -120,6 +120,7 @@ public class CollectionBinder { new DependentKeyValueBinder(simpleValueBinder, compositeIdentifierToManyToOneBinder), simpleValueColumnBinder), new BidirectionalMapElementBinder(manyToOneBinder, collectionForPropertyConfigBinder), + new ManyToManyElementBinder(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/CollectionSecondPassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java index ee69807823..501f49d55b 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 @@ -18,13 +18,10 @@ */ package org.grails.orm.hibernate.cfg.domainbinding.secondpass; -import static org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsDomainBinder.*; - import jakarta.annotation.Nonnull; import java.util.*; import java.util.Map; 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.HibernateManyToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; @@ -40,7 +37,7 @@ public class CollectionSecondPassBinder { private final CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder; private final CollectionKeyBinder collectionKeyBinder; private final BidirectionalMapElementBinder bidirectionalMapElementBinder; - private final ManyToOneBinder manyToOneBinder; + private final ManyToManyElementBinder manyToManyElementBinder; private final PrimaryKeyValueCreator primaryKeyValueCreator; private final CollectionKeyColumnUpdater collectionKeyColumnUpdater; private final UnidirectionalOneToManyBinder unidirectionalOneToManyBinder; @@ -49,7 +46,6 @@ public class CollectionSecondPassBinder { /** Creates a new {@link CollectionSecondPassBinder} instance. */ public CollectionSecondPassBinder( - ManyToOneBinder manyToOneBinder, PrimaryKeyValueCreator primaryKeyValueCreator, CollectionKeyColumnUpdater collectionKeyColumnUpdater, UnidirectionalOneToManyBinder unidirectionalOneToManyBinder, @@ -57,9 +53,9 @@ public class CollectionSecondPassBinder { CollectionForPropertyConfigBinder collectionForPropertyConfigBinder, CollectionKeyBinder collectionKeyBinder, BidirectionalMapElementBinder bidirectionalMapElementBinder, + ManyToManyElementBinder manyToManyElementBinder, CollectionOrderByBinder collectionOrderByBinder, CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder) { - this.manyToOneBinder = manyToOneBinder; this.primaryKeyValueCreator = primaryKeyValueCreator; this.collectionKeyColumnUpdater = collectionKeyColumnUpdater; this.unidirectionalOneToManyBinder = unidirectionalOneToManyBinder; @@ -67,6 +63,7 @@ public class CollectionSecondPassBinder { this.collectionForPropertyConfigBinder = collectionForPropertyConfigBinder; this.collectionKeyBinder = collectionKeyBinder; this.bidirectionalMapElementBinder = bidirectionalMapElementBinder; + this.manyToManyElementBinder = manyToManyElementBinder; this.collectionOrderByBinder = collectionOrderByBinder; this.collectionMultiTenantFilterBinder = collectionMultiTenantFilterBinder; } @@ -119,7 +116,7 @@ public class CollectionSecondPassBinder { InFlightMetadataCollector mappings, Collection collection) { if (property instanceof HibernateManyToManyProperty manyToMany && manyToMany.isBidirectional()) { - bindManyToManyElement(manyToMany, collection); + manyToManyElementBinder.bind(manyToMany, collection); } else if (property.isBidirectionalOneToManyMap() && property.isBidirectional()) { bidirectionalMapElementBinder.bind(property, collection); } else if (property instanceof HibernateOneToManyProperty oneToManyProperty && oneToManyProperty.isUnidirectionalOneToMany()) { @@ -129,19 +126,6 @@ public class CollectionSecondPassBinder { } } - private void bindManyToManyElement( - HibernateManyToManyProperty manyToMany, Collection collection) { - HibernateManyToManyProperty otherSide = manyToMany.getHibernateInverseSide(); - ManyToOne element = - manyToOneBinder.bindManyToOne(otherSide, collection.getCollectionTable(), EMPTY_PATH); - element.setReferencedEntityName(otherSide.getOwner().getName()); - collection.setElement(element); - collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, manyToMany); - if (manyToMany.isCircular()) { - collection.setInverse(false); - } - } - private @Nonnull PersistentClass resolveAssociatedClass( HibernateToManyProperty property, Map<?, ?> persistentClasses) { return Optional.ofNullable(property.getHibernateAssociatedEntity()) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinder.java new file mode 100644 index 0000000000..9d4aa3654b --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinder.java @@ -0,0 +1,55 @@ +/* + * 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.HibernateManyToManyProperty; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.ManyToOne; + +/** Binds the element of a bidirectional many-to-many association. */ +public class ManyToManyElementBinder { + + private final ManyToOneBinder manyToOneBinder; + private final CollectionForPropertyConfigBinder collectionForPropertyConfigBinder; + + /** Creates a new {@link ManyToManyElementBinder} instance. */ + public ManyToManyElementBinder( + ManyToOneBinder manyToOneBinder, + CollectionForPropertyConfigBinder collectionForPropertyConfigBinder) { + this.manyToOneBinder = manyToOneBinder; + this.collectionForPropertyConfigBinder = collectionForPropertyConfigBinder; + } + + /** Binds the ManyToOne element for a bidirectional many-to-many collection. */ + public void bind(HibernateManyToManyProperty manyToMany, Collection collection) { + HibernateManyToManyProperty otherSide = manyToMany.getHibernateInverseSide(); + ManyToOne element = + manyToOneBinder.bindManyToOne(otherSide, collection.getCollectionTable(), EMPTY_PATH); + element.setReferencedEntityName(otherSide.getOwner().getName()); + collection.setElement(element); + collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, manyToMany); + if (manyToMany.isCircular()) { + collection.setInverse(false); + } + } +} 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 49f127415d..4aa8190304 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 BidirectionalMapElementBinder(mtob, cfpcb), new CollectionOrderByBinder(), new CollectionMultiTenantFilterBinder(dcnf)) + binder = new CollectionSecondPassBinder(pkvc, cku, uotmb, cwjtb, cfpcb, new CollectionKeyBinder(botml, dkvb, svcb), new BidirectionalMapElementBinder(mtob, cfpcb), new ManyToManyElementBinder(mtob, cfpcb), new CollectionOrderByBinder(), new CollectionMultiTenantFilterBinder(dcnf)) } protected HibernatePersistentProperty createTestHibernateToManyProperty(Class<?> domainClass = CSPBTestEntityWithMany, String propertyName = "items") { diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinderSpec.groovy new file mode 100644 index 0000000000..af397f4a2a --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinderSpec.groovy @@ -0,0 +1,99 @@ +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.HibernateManyToManyProperty +import org.hibernate.mapping.Bag +import org.hibernate.mapping.ManyToOne +import org.hibernate.mapping.Table +import spock.lang.Subject + +class ManyToManyElementBinderSpec extends HibernateGormDatastoreSpec { + + @Subject + ManyToManyElementBinder binder + + void setupSpec() { + manager.addAllDomainClasses([ + MTMEOwner, + MTMEItem, + MTMEBase, + MTMESubtype, + ]) + } + + 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 ManyToManyElementBinder(mtob, new CollectionForPropertyConfigBinder()) + } + + private HibernateManyToManyProperty propertyFor(Class ownerClass, String name = "items") { + (getPersistentEntity(ownerClass) as GrailsHibernatePersistentEntity).getPropertyByName(name) as HibernateManyToManyProperty + } + + def "bind sets ManyToOne element referencing the inverse owner for a standard bidirectional many-to-many"() { + given: + def property = propertyFor(MTMEOwner) + def mbc = getGrailsDomainBinder().getMetadataBuildingContext() + def collection = new Bag(mbc, null) + collection.setCollectionTable(new Table("test", "mtme_owner_mtme_item")) + + when: + binder.bind(property, collection) + + then: + collection.getElement() instanceof ManyToOne + (collection.getElement() as ManyToOne).getReferencedEntityName() == MTMEItem.name + } + + def "bind sets collection inverse false for a circular many-to-many"() { + given: + def property = propertyFor(MTMESubtype, "related") + def mbc = getGrailsDomainBinder().getMetadataBuildingContext() + def collection = new Bag(mbc, null) + collection.setCollectionTable(new Table("test", "mtme_subtype_mtme_base")) + + when: + binder.bind(property, collection) + + then: + property.isCircular() + !collection.isInverse() + } +} + +@Entity +class MTMEOwner { + Long id + static hasMany = [items: MTMEItem] +} + +@Entity +class MTMEItem { + Long id + String description + static hasMany = [owners: MTMEOwner] +} + +@Entity +class MTMEBase { + Long id + static hasMany = [subtypes: MTMESubtype] +} + +@Entity +class MTMESubtype extends MTMEBase { + static hasMany = [related: MTMEBase] +}
