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/04/06 19:16:53 UTC

(grails-core) 03/12: H7 Features & Refactoring * Hierarchy-Based Binding: Refactored CollectionSecondPassBinder and CollectionMultiTenantFilterBinder to use the HibernateToManyEntityProperty and HibernateToManyCollectionProperty sub-interfaces for logical grouping. * Property API Extension: Added isManyToMany() and isOneToMany() default methods to HibernateToManyProperty, replacing brittle implementation-level checks in the binders. * Unified Enum Detection: Implemented a default is [...]

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 f6e917712682b5c70ee001c357e6e695d18fa17f
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Apr 4 18:49:58 2026 -0500

    H7   Features & Refactoring
          * Hierarchy-Based Binding: Refactored CollectionSecondPassBinder and CollectionMultiTenantFilterBinder to use the HibernateToManyEntityProperty and HibernateToManyCollectionProperty sub-interfaces
            for logical grouping.
          * Property API Extension: Added isManyToMany() and isOneToMany() default methods to HibernateToManyProperty, replacing brittle implementation-level checks in the binders.
          * Unified Enum Detection: Implemented a default isEnum() method in the HibernatePersistentProperty base interface to provide a consistent classification API across all property types.
---
 .../cfg/domainbinding/binder/CollectionBinder.java |  10 +-
 .../hibernate/HibernateAssociation.java            |   2 +-
 .../hibernate/HibernateBasicProperty.java          |   2 +-
 .../HibernateEmbeddedCollectionProperty.java       |   2 +-
 .../hibernate/HibernateManyToManyProperty.java     |   2 +-
 .../hibernate/HibernateOneToManyProperty.java      |   2 +-
 .../hibernate/HibernatePersistentProperty.java     |   7 +
 .../HibernateToManyCollectionProperty.java         |   5 +
 ...rty.java => HibernateToManyEntityProperty.java} |   2 +-
 .../hibernate/HibernateToManyProperty.java         |  12 +-
 ...der.java => CollectionEntityOrderByBinder.java} |  10 +-
 .../CollectionMultiTenantFilterBinder.java         |  19 ++-
 .../secondpass/CollectionSecondPassBinder.java     |  33 ++---
 .../HibernatePersistentPropertySpec.groovy         | 153 ++++++++++-----------
 .../BidirectionalMapElementBinderSpec.groovy       |   2 +-
 ...vy => CollectionEntityOrderByBinderSpec.groovy} |  13 +-
 .../CollectionSecondPassBinderSpec.groovy          |  18 +--
 .../datastore/mapping/model/types/Association.java |   2 +-
 18 files changed, 156 insertions(+), 140 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 04c8aed198..c1f69d73e3 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,7 +32,7 @@ import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy;
 import org.grails.orm.hibernate.cfg.PropertyConfig;
 import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder;
 import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionType;
-import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateCollectionProperty;
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyEntityProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernatePersistentProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.BasicCollectionElementBinder;
@@ -41,7 +41,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.secondpass.BidirectionalOneToM
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionKeyBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionKeyColumnUpdater;
 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.CollectionEntityOrderByBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPassBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionWithJoinTableBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.secondpass.DependentKeyValueBinder;
@@ -119,7 +119,7 @@ public class CollectionBinder {
                 collectionForPropertyConfigBinder,
                 new BidirectionalMapElementBinder(manyToOneBinder, collectionForPropertyConfigBinder),
                 new ManyToManyElementBinder(manyToOneBinder, collectionForPropertyConfigBinder),
-                new CollectionOrderByBinder(),
+                new CollectionEntityOrderByBinder(),
                 new CollectionMultiTenantFilterBinder(new DefaultColumnNameFetcher(namingStrategy)));
         this.listSecondPassBinder = new ListSecondPassBinder(
                 metadataBuildingContext, namingStrategy, collectionSecondPassBinder, simpleValueColumnBinder, mappings);
@@ -167,7 +167,7 @@ public class CollectionBinder {
         if (property.shouldBindWithForeignKey()) {
             OneToMany oneToMany = new OneToMany(metadataBuildingContext, collection.getOwner());
             collection.setElement(oneToMany);
-            bindOneToMany((HibernateCollectionProperty) property, oneToMany);
+            bindOneToMany((HibernateToManyEntityProperty) property, oneToMany);
         } else {
             bindCollectionTable(property, _persistentClass.getTable());
 
@@ -203,7 +203,7 @@ public class CollectionBinder {
         return property.getName();
     }
 
-    private void bindOneToMany(HibernateCollectionProperty currentGrailsProp, OneToMany one) {
+    private void bindOneToMany(HibernateToManyEntityProperty currentGrailsProp, OneToMany one) {
         one.setReferencedEntityName(
                 currentGrailsProp.getHibernateAssociatedEntity().getName());
         one.setIgnoreNotFound(true);
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java
index 800ec30d11..6e74814f07 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java
@@ -53,7 +53,7 @@ public interface HibernateAssociation extends HibernatePersistentProperty {
 
     boolean isCircular();
 
-    boolean isBidirectionalOneToManyMap();
+    boolean isBidirectionalToManyMap();
 
     /**
      * Returns the nullable value for the FK column when this property is an association without a
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java
index 16d34ab72c..565781defc 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java
@@ -27,7 +27,7 @@ import org.grails.datastore.mapping.model.types.mapping.BasicWithMapping;
 import org.grails.orm.hibernate.cfg.PropertyConfig;
 
 /** Hibernate implementation of {@link org.grails.datastore.mapping.model.types.Basic} */
-public class HibernateBasicProperty extends BasicWithMapping<PropertyConfig> implements HibernateToManyProperty {
+public class HibernateBasicProperty extends BasicWithMapping<PropertyConfig> implements HibernateToManyCollectionProperty {
 
     private Collection collection;
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java
index fb6286ada8..0769b42ba6 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java
@@ -31,7 +31,7 @@ import org.grails.orm.hibernate.cfg.PropertyConfig;
  * Hibernate implementation of {@link org.grails.datastore.mapping.model.types.EmbeddedCollection}
  */
 public class HibernateEmbeddedCollectionProperty extends EmbeddedCollectionWithMapping<PropertyConfig>
-        implements HibernateToManyProperty {
+        implements HibernateToManyCollectionProperty {
 
     private Collection collection;
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java
index 7e743a40ee..488655ba96 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java
@@ -29,7 +29,7 @@ import org.grails.orm.hibernate.cfg.PropertyConfig;
 
 /** Hibernate implementation of {@link org.grails.datastore.mapping.model.types.ManyToMany} */
 public class HibernateManyToManyProperty extends ManyToManyWithMapping<PropertyConfig>
-        implements HibernateCollectionProperty {
+        implements HibernateToManyEntityProperty {
 
     private Collection collection;
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java
index a194983338..fc8bda609a 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToManyProperty.java
@@ -30,7 +30,7 @@ import org.grails.orm.hibernate.cfg.PropertyConfig;
 
 /** Hibernate implementation of {@link org.grails.datastore.mapping.model.types.OneToMany} */
 public class HibernateOneToManyProperty extends OneToManyWithMapping<PropertyConfig>
-        implements HibernateCollectionProperty {
+        implements HibernateToManyEntityProperty {
 
     private Collection collection;
 
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentProperty.java
index 6d55fe32f6..dc31cdb3cf 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentProperty.java
@@ -134,6 +134,13 @@ public interface HibernatePersistentProperty extends PersistentProperty<Property
         return Optional.ofNullable(getType()).map(Class::isEnum).orElse(false);
     }
 
+    /**
+     * @return Whether this property is an enum property.
+     */
+    default boolean isEnum() {
+        return this instanceof HibernateEnumProperty;
+    }
+
     default boolean isValidHibernateOneToOne() {
         return false;
     }
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyCollectionProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyCollectionProperty.java
new file mode 100644
index 0000000000..add950220d
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyCollectionProperty.java
@@ -0,0 +1,5 @@
+package org.grails.orm.hibernate.cfg.domainbinding.hibernate;
+
+public interface HibernateToManyCollectionProperty extends HibernateToManyProperty{
+
+}
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateCollectionProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyEntityProperty.java
similarity index 94%
rename from grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateCollectionProperty.java
rename to grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyEntityProperty.java
index 01a0dca341..6f9c704436 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateCollectionProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyEntityProperty.java
@@ -24,7 +24,7 @@ import org.hibernate.mapping.PersistentClass;
 /**
  * Marker interface for Hibernate Collections
  */
-public interface HibernateCollectionProperty extends HibernateToManyProperty {
+public interface HibernateToManyEntityProperty extends HibernateToManyProperty {
 
     @Override
     HibernatePersistentEntity getHibernateAssociatedEntity();
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
index 288a626121..4976eb08d3 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
@@ -79,6 +79,14 @@ public interface HibernateToManyProperty extends PropertyWithMapping<PropertyCon
         return this instanceof Basic;
     }
 
+    default boolean isManyToMany() {
+        return this instanceof HibernateManyToManyProperty;
+    }
+
+    default boolean isOneToMany() {
+        return this instanceof HibernateOneToManyProperty;
+    }
+
     /**
      * Returns the component type for basic (scalar/enum) collections, or {@code null} if this is not
      * a basic collection.
@@ -223,8 +231,10 @@ public interface HibernateToManyProperty extends PropertyWithMapping<PropertyCon
         return Optional.ofNullable(getHibernateMappedForm()).map(PropertyConfig::getJoinTableColumnConfig);
     }
 
+    @Override
     default boolean isEnum() {
-        return getComponentType().isEnum();
+        Class<?> componentType = getComponentType();
+        return componentType != null && componentType.isEnum();
     }
 
     /**
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionOrderByBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionEntityOrderByBinder.java
similarity index 93%
rename from grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionOrderByBinder.java
rename to grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionEntityOrderByBinder.java
index 60da182a0c..ab9a343b9a 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionOrderByBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionEntityOrderByBinder.java
@@ -27,24 +27,24 @@ import org.hibernate.mapping.PersistentClass;
 
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity;
-import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateCollectionProperty;
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyEntityProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernatePersistentProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.util.OrderByClauseBuilder;
 
 /** Binds the order-by clause and discriminator where condition to a collection. */
-public class CollectionOrderByBinder {
+public class CollectionEntityOrderByBinder {
 
     private final OrderByClauseBuilder orderByClauseBuilder;
     private final CollectionForPropertyConfigBinder collectionForPropertyConfigBinder;
 
-    /** Creates a new {@link CollectionOrderByBinder} instance. */
-    public CollectionOrderByBinder() {
+    /** Creates a new {@link CollectionEntityOrderByBinder} instance. */
+    public CollectionEntityOrderByBinder() {
         this.orderByClauseBuilder = new OrderByClauseBuilder();
         this.collectionForPropertyConfigBinder = new CollectionForPropertyConfigBinder();
     }
 
     /** Binds the order-by clause and discriminator where condition to the given collection. */
-    public void bind(HibernateCollectionProperty property) {
+    public void bind(HibernateToManyEntityProperty property) {
         Collection collection = property.getCollection();
         PersistentClass associatedClass = property.getAssociatedClass();
         GrailsHibernatePersistentEntity referenced = property.getHibernateAssociatedEntity();
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinder.java
index 25f006d573..f8be0b2338 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinder.java
@@ -24,7 +24,9 @@ import java.util.Optional;
 import org.hibernate.mapping.Collection;
 
 import org.grails.datastore.mapping.model.config.GormProperties;
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToManyProperty;
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyEntityProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty;
 import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher;
 
@@ -40,12 +42,13 @@ public class CollectionMultiTenantFilterBinder {
 
     /** Applies the multi-tenant filter to the collection if the associated entity is multi-tenant. */
     public void bind(HibernateToManyProperty property) {
-        Collection collection = property.getCollection();
-        Optional.ofNullable(property.getHibernateAssociatedEntity())
-                .filter(referenced -> !(property instanceof HibernateManyToManyProperty) && referenced.isMultiTenant())
-                .map(referenced -> referenced.getMultiTenantFilterCondition(defaultColumnNameFetcher))
-                .ifPresent(filterCondition -> {
-                    if (property.isUnidirectionalOneToMany()) {
+        if (property instanceof HibernateToManyEntityProperty entityProperty) {
+            GrailsHibernatePersistentEntity referenced = entityProperty.getHibernateAssociatedEntity();
+            if (entityProperty.isOneToMany() && referenced.isMultiTenant()) {
+                String filterCondition = referenced.getMultiTenantFilterCondition(defaultColumnNameFetcher);
+                if (filterCondition != null) {
+                    Collection collection = property.getCollection();
+                    if (entityProperty.isUnidirectionalOneToMany()) {
                         collection.addManyToManyFilter(
                                 GormProperties.TENANT_IDENTITY,
                                 filterCondition,
@@ -60,6 +63,8 @@ public class CollectionMultiTenantFilterBinder {
                                 Collections.emptyMap(),
                                 Collections.emptyMap());
                     }
-                });
+                }
+            }
+        }
     }
 }
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 4333149d1d..8b2cb7d749 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
@@ -26,7 +26,8 @@ import org.hibernate.mapping.Collection;
 import org.hibernate.mapping.PersistentClass;
 
 import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder;
-import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateCollectionProperty;
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyCollectionProperty;
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyEntityProperty;
 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;
@@ -37,7 +38,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyPrope
 @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
 public class CollectionSecondPassBinder {
 
-    private final CollectionOrderByBinder collectionOrderByBinder;
+    private final CollectionEntityOrderByBinder collectionEntityOrderByBinder;
     private final CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder;
     private final CollectionKeyColumnUpdater collectionKeyColumnUpdater;
     private final BidirectionalMapElementBinder bidirectionalMapElementBinder;
@@ -53,7 +54,7 @@ public class CollectionSecondPassBinder {
             CollectionForPropertyConfigBinder collectionForPropertyConfigBinder,
             BidirectionalMapElementBinder bidirectionalMapElementBinder,
             ManyToManyElementBinder manyToManyElementBinder,
-            CollectionOrderByBinder collectionOrderByBinder,
+            CollectionEntityOrderByBinder collectionEntityOrderByBinder,
             CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder) {
         this.collectionKeyColumnUpdater = collectionKeyColumnUpdater;
         this.unidirectionalOneToManyBinder = unidirectionalOneToManyBinder;
@@ -61,7 +62,7 @@ public class CollectionSecondPassBinder {
         this.collectionForPropertyConfigBinder = collectionForPropertyConfigBinder;
         this.bidirectionalMapElementBinder = bidirectionalMapElementBinder;
         this.manyToManyElementBinder = manyToManyElementBinder;
-        this.collectionOrderByBinder = collectionOrderByBinder;
+        this.collectionEntityOrderByBinder = collectionEntityOrderByBinder;
         this.collectionMultiTenantFilterBinder = collectionMultiTenantFilterBinder;
     }
 
@@ -69,18 +70,18 @@ public class CollectionSecondPassBinder {
 
         PersistentClass ownerClass = property.getHibernateOwner().getPersistentClass();
 
-        if (property instanceof HibernateCollectionProperty collectionProperty) {
-            collectionOrderByBinder.bind(collectionProperty);
-        }
-        if (property instanceof HibernateManyToManyProperty manyToMany && manyToMany.isBidirectional()) {
-            manyToManyElementBinder.bind(manyToMany);
-        } else if (property.isBidirectionalToManyMap() && property.isBidirectional()) {
-            bidirectionalMapElementBinder.bind(property);
-        } else if (property instanceof HibernateOneToManyProperty oneToManyProperty &&
-            oneToManyProperty.isUnidirectionalOneToMany()) {
-            unidirectionalOneToManyBinder.bind(oneToManyProperty);
-        } else if (property.supportsJoinColumnMapping()) {
-            collectionWithJoinTableBinder.bindCollectionWithJoinTable(property);
+        if (property instanceof HibernateToManyEntityProperty entityProperty) {
+            collectionEntityOrderByBinder.bind(entityProperty);
+            if (entityProperty.isManyToMany() && entityProperty.isBidirectional()) {
+                manyToManyElementBinder.bind((HibernateManyToManyProperty) entityProperty);
+            } else if (entityProperty.isBidirectionalToManyMap() && entityProperty.isBidirectional()) {
+                bidirectionalMapElementBinder.bind(entityProperty);
+            } else if (entityProperty.isOneToMany() && entityProperty.isUnidirectionalOneToMany()) {
+                unidirectionalOneToManyBinder.bind((HibernateOneToManyProperty) entityProperty);
+            }
+        } else if (property instanceof HibernateToManyCollectionProperty collectionProperty &&
+                collectionProperty.supportsJoinColumnMapping()) {
+            collectionWithJoinTableBinder.bindCollectionWithJoinTable(collectionProperty);
         }
         collectionMultiTenantFilterBinder.bind(property);
         collectionKeyColumnUpdater.bind(property, ownerClass);
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentPropertySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentPropertySpec.groovy
index 2b71af76f3..4284d5d738 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentPropertySpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernatePersistentPropertySpec.groovy
@@ -28,7 +28,8 @@ class HibernatePersistentPropertySpec extends HibernateGormDatastoreSpec {
     void setupSpec() {
         manager.addAllDomainClasses([
             LazyBook, LazyAuthor, ExplicitNonLazy, JoinFetchEntity, EnumEntity,
-            GeneratorDefaultEntity, GeneratorUuid2Entity, CompositeKeyEntity
+            GeneratorDefaultEntity, GeneratorUuid2Entity, CompositeKeyEntity,
+            HPPSManyA, HPPSManyB
         ])
     }
 
@@ -60,68 +61,52 @@ class HibernatePersistentPropertySpec extends HibernateGormDatastoreSpec {
         property.isLazy()
     }
 
-    def "test isLazy for association"() {
+    def "test isLazy for collection with fetch join"() {
         given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(LazyBook.name)
-        def property = (HibernatePersistentProperty) entity.getPropertyByName("author")
-
-        expect:
-        property.isLazyAble()
-        property.isLazy()
-    }
-
-    def "test isLazy for explicit lazy false"() {
-        given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(ExplicitNonLazy.name)
-        def property = (HibernatePersistentProperty) entity.getPropertyByName("author")
+        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(JoinFetchEntity.name)
+        def property = (HibernatePersistentProperty) entity.getPropertyByName("items")
 
         expect:
         !property.isLazy()
     }
 
-    def "test isLazy for fetch join"() {
+    def "test isLazy for explicit non-lazy"() {
         given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(JoinFetchEntity.name)
-        def property = (HibernatePersistentProperty) entity.getPropertyByName("author")
+        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(ExplicitNonLazy.name)
+        def property = (HibernatePersistentProperty) entity.getPropertyByName("name")
 
         expect:
         !property.isLazy()
     }
 
-    def "test getTypeName"() {
+    def "test getHibernateOwner"() {
         given:
         def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(LazyBook.name)
         def property = (HibernatePersistentProperty) entity.getPropertyByName("title")
 
         expect:
-        property.getTypeName() == String.name
+        property.getHibernateOwner() == entity
     }
 
-    def "test isEnumType"() {
+    def "test isEnum"() {
         given:
         def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(EnumEntity.name)
-        def property = (HibernatePersistentProperty) entity.getPropertyByName("status")
-
-        expect:
-        property.isEnumType()
-    }
-
-    def "test getHibernateOwner"() {
-        given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(LazyBook.name)
-        def property = (HibernatePersistentProperty) entity.getPropertyByName("title")
+        def enumProp = (HibernatePersistentProperty) entity.getPropertyByName("type")
+        def stringProp = (HibernatePersistentProperty) entity.getPropertyByName("name")
 
         expect:
-        property.getHibernateOwner() == entity
+        enumProp.isEnum()
+        !stringProp.isEnum()
     }
 
-    def "test isJoinKeyMapped"() {
+    def "test getGeneratorName"() {
         given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(JoinFetchEntity.name)
-        def property = (HibernatePersistentProperty) entity.getPropertyByName("author")
+        def defaultEntity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(GeneratorDefaultEntity.name)
+        def uuidEntity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(GeneratorUuid2Entity.name)
 
         expect:
-        !property.isJoinKeyMapped() // Default many-to-one doesn't have join table usually
+        defaultEntity.getIdentityProperty().getGeneratorName() == "identity"
+        uuidEntity.getIdentityProperty().getGeneratorName() == "uuid2"
     }
 
     def "test getPersistentClass"() {
@@ -146,6 +131,22 @@ class HibernatePersistentPropertySpec extends HibernateGormDatastoreSpec {
         result == property
     }
 
+    def "test isManyToMany and isOneToMany"() {
+        given:
+        def authorEntity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(LazyAuthor.name)
+        def entityA = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(HPPSManyA.name)
+
+        def oneToMany = (HibernateToManyProperty) authorEntity.getPropertyByName("books")
+        def manyToMany = (HibernateToManyProperty) entityA.getPropertyByName("others")
+
+        expect:
+        oneToMany.isOneToMany()
+        !oneToMany.isManyToMany()
+
+        manyToMany.isManyToMany()
+        !manyToMany.isOneToMany()
+    }
+
     def "getIdentityProperty returns HibernateCompositeIdentityProperty with all parts for composite key entity"() {
         given:
         def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(CompositeKeyEntity.name)
@@ -160,43 +161,13 @@ class HibernatePersistentPropertySpec extends HibernateGormDatastoreSpec {
         parts != null
         parts.length == 2
         parts.every { it instanceof HibernatePersistentProperty }
-        parts*.name.toSet() == ['firstName', 'lastName'].toSet()
-    }
-
-
-    def "getGeneratorName returns null for regular property with no generator configured"() {
-        given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(GeneratorDefaultEntity.name)
-        def property = (HibernatePersistentProperty) entity.getPropertyByName("name")
-
-        expect:
-        property.getGeneratorName() == null
-    }
-
-    def "getGeneratorName on identity with no explicit generator delegates to entity"() {
-        given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(GeneratorDefaultEntity.name)
-        def identity = (HibernateSimpleIdentityProperty) entity.getIdentityProperty()
-
-        expect:
-        identity.getGeneratorName() == entity.getIdentityGeneratorName()
-        identity.getGeneratorName() != null
-    }
-
-    def "getGeneratorName on identity with explicit generator returns that generator"() {
-        given:
-        def entity = (HibernatePersistentEntity) getMappingContext().getPersistentEntity(GeneratorUuid2Entity.name)
-        def identity = (HibernateSimpleIdentityProperty) entity.getIdentityProperty()
-
-        expect:
-        identity.getGeneratorName() == GrailsSequenceGeneratorEnum.UUID2.toString()
+        parts*.name.sort() == ["code", "name"]
     }
 }
 
 @Entity
 class LazyBook {
     Long id
-    Long version
     String title
     LazyAuthor author
 }
@@ -211,37 +182,28 @@ class LazyAuthor {
 @Entity
 class ExplicitNonLazy {
     Long id
-    LazyAuthor author
+    String name
     static mapping = {
-        author lazy: false
+        name lazy: false
     }
+
+    boolean isLazyFalse() { false }
 }
 
 @Entity
 class JoinFetchEntity {
     Long id
-    LazyAuthor author
+    static hasMany = [items: String]
     static mapping = {
-        author fetch: 'join'
+        items fetch: "join"
     }
 }
 
-enum Status { ACTIVE, INACTIVE }
-
 @Entity
 class EnumEntity {
     Long id
-    Status status
-}
-
-@Entity
-class CompositeKeyEntity implements Serializable {
-    String firstName
-    String lastName
-    Long version
-    static mapping = {
-        id composite: ['firstName', 'lastName']
-    }
+    String name
+    GrailsSequenceGeneratorEnum type
 }
 
 @Entity
@@ -258,3 +220,28 @@ class GeneratorUuid2Entity {
         id generator: 'uuid2'
     }
 }
+
+@Entity
+class CompositeKeyEntity implements Serializable {
+    String name
+    Integer code
+    static mapping = {
+        id composite: ['name', 'code']
+    }
+}
+
+@Entity
+class HPPSManyA {
+    Long id
+    static hasMany = [others: HPPSManyB]
+    static mapping = {
+        others joinTable: [name: 'many_a_others']
+    }
+}
+
+@Entity
+class HPPSManyB {
+    Long id
+    static hasMany = [owners: HPPSManyA]
+    static belongsTo = [owners: HPPSManyA]
+}
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
index 29322c3325..b56aae908c 100644
--- 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
@@ -81,7 +81,7 @@ class BidirectionalMapElementBinderSpec extends HibernateGormDatastoreSpec {
         def property = propertyFor(BBMEOwner)
 
         expect:
-        property.isBidirectionalOneToManyMap()
+        property.isBidirectionalToManyMap()
         property.isBidirectional()
     }
 }
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionOrderByBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionEntityOrderByBinderSpec.groovy
similarity index 93%
rename from grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionOrderByBinderSpec.groovy
rename to grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionEntityOrderByBinderSpec.groovy
index 1dfeaf04ef..dee029c6e8 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionOrderByBinderSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionEntityOrderByBinderSpec.groovy
@@ -21,8 +21,8 @@ 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.hibernate.GrailsHibernatePersistentEntity
-import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateCollectionProperty
-import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyEntityProperty
+
 import org.hibernate.mapping.Bag
 import org.hibernate.mapping.BasicValue
 import org.hibernate.mapping.Column
@@ -32,10 +32,11 @@ import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.Table
 import spock.lang.Subject
 
-class CollectionOrderByBinderSpec extends HibernateGormDatastoreSpec {
+class CollectionEntityOrderByBinderSpec extends HibernateGormDatastoreSpec {
 
     @Subject
-    CollectionOrderByBinder binder = new CollectionOrderByBinder()
+    CollectionEntityOrderByBinder binder = new CollectionEntityOrderByBinder()
+
 
     void setupSpec() {
         manager.addAllDomainClasses([
@@ -48,8 +49,8 @@ class CollectionOrderByBinderSpec extends HibernateGormDatastoreSpec {
         ])
     }
 
-    private HibernateCollectionProperty propertyFor(Class ownerClass, String name = "items") {
-        (getPersistentEntity(ownerClass) as GrailsHibernatePersistentEntity).getPropertyByName(name) as HibernateCollectionProperty
+    private HibernateToManyEntityProperty propertyFor(Class ownerClass, String name = "items") {
+        (getPersistentEntity(ownerClass) as GrailsHibernatePersistentEntity).getPropertyByName(name) as HibernateToManyEntityProperty
     }
 
     private RootClass rootClassWith(String entityName, String propertyName, String columnName) {
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 0e1ad43411..c687c46504 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
@@ -27,7 +27,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.hibernate.HibernateManyToManyProperty
 import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty
-import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateCollectionProperty
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyEntityProperty
 
 import org.hibernate.mapping.ManyToOne
 
@@ -66,7 +66,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
         def svcb = new SimpleValueColumnBinder()
         def cku = new CollectionKeyColumnUpdater(new CollectionKeyBinder(botml, dkvb, svcb, pkvc))
 
-        binder = new CollectionSecondPassBinder(cku, uotmb, cwjtb, cfpcb, mockBidirectionalMapElementBinder, new ManyToManyElementBinder(mtob, cfpcb), new CollectionOrderByBinder(), new CollectionMultiTenantFilterBinder(dcnf))
+        binder = new CollectionSecondPassBinder(cku, uotmb, cwjtb, cfpcb, mockBidirectionalMapElementBinder, new ManyToManyElementBinder(mtob, cfpcb), new CollectionEntityOrderByBinder(), new CollectionMultiTenantFilterBinder(dcnf))
     }
 
     protected HibernatePersistentProperty createTestHibernateToManyProperty(Class<?> domainClass, String propertyName) {
@@ -90,7 +90,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         then:
         noExceptionThrown()
-        !(property instanceof HibernateCollectionProperty)
+        !(property instanceof HibernateToManyEntityProperty)
     }
 
     def "bindCollectionSecondPass succeeds for Unidirectional One-to-Many"() {
@@ -105,7 +105,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         then:
         noExceptionThrown()
-        property instanceof HibernateCollectionProperty
+        property instanceof HibernateToManyEntityProperty
         property.getCollection() != null
     }
 
@@ -122,7 +122,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         then:
         noExceptionThrown()
-        property instanceof HibernateCollectionProperty
+        property instanceof HibernateToManyEntityProperty
         property.isBidirectional()
         // In Hibernate 7 many-to-many element is mapped as ManyToOne to the join table
         property.getCollection().getElement() instanceof ManyToOne
@@ -140,7 +140,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         then:
         noExceptionThrown()
-        property instanceof HibernateCollectionProperty
+        property instanceof HibernateToManyEntityProperty
         property.getCollection().getOrderBy() != null
     }
 
@@ -156,7 +156,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
         then:
         noExceptionThrown()
-        !(property instanceof HibernateCollectionProperty)
+        !(property instanceof HibernateToManyEntityProperty)
         property.getCollection() != null
     }
 
@@ -179,10 +179,10 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
 
     def "HibernateCollectionProperty getAssociatedClass returns PersistentClass or throws MappingException"() {
         given: "A collection property that implements HibernateCollectionProperty"
-        def property = createTestHibernateToManyProperty(CSPBUniOwner, "items") as HibernateCollectionProperty
+        def property = createTestHibernateToManyProperty(CSPBUniOwner, "items") as HibernateToManyEntityProperty
         
         expect:
-        property instanceof HibernateCollectionProperty
+        property instanceof HibernateToManyEntityProperty
 
         when: "Persistent class is present (after first pass)"
         hibernateFirstPass()
diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/types/Association.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/types/Association.java
index 1a83c09a51..02ad87e5e0 100644
--- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/types/Association.java
+++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/types/Association.java
@@ -369,7 +369,7 @@ public abstract class Association<T extends Property> extends AbstractPersistent
                 .orElse(false);
     }
 
-    public boolean isBidirectionalOneToManyMap() {
+    public boolean isBidirectionalToManyMap() {
         return Map.class.isAssignableFrom(this.getType()) && this.isBidirectional();
     }
 }