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 439fd2a627899609529b1ef582dcafb38347cefa
Author: Walter B Duque de Estrada <[email protected]>
AuthorDate: Thu Jan 15 10:53:47 2026 -0600

    update progress
---
 .../core/HIBERNATE7-UPGRADE-PROGRESS.md            |  18 +++
 .../hibernate/cfg/AbstractGrailsDomainBinder.java  |  78 -----------
 .../orm/hibernate/cfg/GrailsDomainBinder.java      | 153 ++++++++++-----------
 .../hibernate/cfg/HibernatePersistentEntity.java   |  19 +++
 .../cfg/domainbinding/RootMappingFetcher.java      |  19 +++
 .../hibernate/query/GrailsHibernateQueryUtils.java |  11 +-
 .../specs/proxy/Hibernate7GroovyProxySpec.groovy   |   1 +
 .../domainbinding/RootMappingFetcherSpec.groovy    |  60 ++++++++
 .../mapping/model/PersistentProperty.java          |  11 ++
 .../datastore/mapping/model/types/Association.java |   4 +
 10 files changed, 205 insertions(+), 169 deletions(-)

diff --git a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md 
b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md
index 1455485455..bec594ac79 100644
--- a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md
+++ b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md
@@ -59,6 +59,24 @@ This document summarizes the approaches taken, challenges 
encountered, and futur
 - **Issue:** Hibernate 7 proxies no longer implement `isInitialized()` or 
`getInitialized()` directly on the proxy object.
 - **Solution:** Switched to `Hibernate.isInitialized(proxy)`.
 
+## Strategy for GrailsDomainBinder Refactoring
+
+### Goal
+To decompose the monolithic `GrailsDomainBinder` (over 2000 lines) into 
smaller, specialized binder classes within the 
`org.grails.orm.hibernate.cfg.domainbinding` package. This improves 
maintainability and enables unit testing of specific binding logic.
+
+### Refactoring Pattern
+Each new binder should follow this structure:
+1.  **Dependencies as Fields:** Define `private final` fields for all 
dependent binders and utility classes.
+2.  **Public Constructor:** A constructor that takes essential state (e.g., 
`PersistentEntityNamingStrategy`) and initializes simple dependencies 
internally.
+3.  **Protected Constructor for Testing:** A second constructor that accepts 
all dependencies as arguments. This allows unit tests to inject mocks for all 
collaborating classes.
+4.  **Core Method:** A public method that contains the logic previously held 
in `GrailsDomainBinder` (e.g., `bindCollectionSecondPass`).
+
+### Testing Strategy
+Unit tests should be created for each new binder class (e.g., 
`CollectionBinderSpec`). These tests should:
+- Use the protected constructor to inject mocks.
+- Verify interactions with dependent binders using Spock's `Mock()` and `1 * 
...` syntax.
+- Ensure that the complex logic of `GrailsDomainBinder` is covered by isolated 
unit tests rather than relying solely on integration tests.
+
 ## Future Steps
 
 1.  **Resolve Proxy Initialization:** Determine why proxies are returning as 
initialized in `Hibernate6GroovyProxySpec`. Investigate if Hibernate 7's 
bytecode enhancement or ByteBuddy factory settings are interfering.
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/AbstractGrailsDomainBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/AbstractGrailsDomainBinder.java
deleted file mode 100644
index f2093bdeaf..0000000000
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/AbstractGrailsDomainBinder.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* Copyright 2004-2005 the original author or authors.
- *
- * Licensed 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
- *
- *      http://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;
-
-import org.grails.datastore.mapping.model.PersistentEntity;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Handles the binding Grails domain classes and properties to the Hibernate 
runtime meta model.
- * Based on the HbmBinder code in Hibernate core and influenced by 
AnnotationsBinder.
- *
- * @author Graeme Rocher
- * @since 0.1
- */
-public abstract class AbstractGrailsDomainBinder {
-    protected static final Map<Class<?>, Mapping> MAPPING_CACHE = new 
HashMap<>();
-
-
-    /**
-     * Obtains a mapping object for the given domain class nam
-     *
-     * @param theClass The domain class in question
-     * @return A Mapping object or null
-     */
-    public static Mapping getMapping(Class<?> theClass) {
-        return theClass == null ? null : MAPPING_CACHE.get(theClass);
-    }
-
-    /**
-     * Obtains a mapping object for the given domain class nam
-     *
-     * @param theClass The domain class in question
-     */
-    public static void cacheMapping(Class<?> theClass, Mapping mapping) {
-        MAPPING_CACHE.put(theClass, mapping);
-    }
-
-    /**
-     * Obtains a mapping object for the given domain class nam
-     *
-     * @param domainClass The domain class in question
-     * @return A Mapping object or null
-     */
-    public static Mapping getMapping(PersistentEntity domainClass) {
-        return domainClass == null ? null : 
MAPPING_CACHE.get(domainClass.getJavaClass());
-    }
-
-
-    public static void clearMappingCache() {
-        MAPPING_CACHE.clear();
-    }
-
-    public static void clearMappingCache(Class<?> theClass) {
-        String className = theClass.getName();
-        for(Iterator<Map.Entry<Class<?>, Mapping>> it = 
MAPPING_CACHE.entrySet().iterator(); it.hasNext();) {
-            Map.Entry<Class<?>, Mapping> entry = it.next();
-            if (className.equals(entry.getKey().getName())) {
-                it.remove();
-            }
-        }
-    }
-}
-
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 8d03295a55..4a36a3e597 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -15,9 +15,8 @@
 package org.grails.orm.hibernate.cfg;
 
 import groovy.lang.Closure;
-import jakarta.persistence.Entity;
+
 import org.grails.datastore.mapping.core.connections.ConnectionSource;
-import org.grails.datastore.mapping.core.connections.ConnectionSourcesSupport;
 import org.grails.datastore.mapping.model.DatastoreConfigurationException;
 import org.grails.datastore.mapping.model.PersistentEntity;
 import org.grails.datastore.mapping.model.PersistentProperty;
@@ -45,7 +44,6 @@ import org.hibernate.boot.internal.RootMappingDefaults;
 import org.hibernate.boot.model.TypeContributions;
 import org.hibernate.boot.model.TypeContributor;
 import org.hibernate.boot.model.internal.BinderHelper;
-import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
 import org.hibernate.boot.spi.AdditionalMappingContributions;
 import org.hibernate.boot.spi.AdditionalMappingContributor;
 import org.hibernate.boot.spi.InFlightMetadataCollector;
@@ -118,6 +116,7 @@ public class GrailsDomainBinder
 {
 
     public static final String FOREIGN_KEY_SUFFIX = "_id";
+    protected static final Map<Class<?>, Mapping> MAPPING_CACHE = new 
HashMap<>();
     private static final String STRING_TYPE = "string";
     private static final String EMPTY_PATH = "";
     public static final char UNDERSCORE = '_';
@@ -149,6 +148,59 @@ public class GrailsDomainBinder
     private PersistentEntityNamingStrategy namingStrategy;
     private MetadataBuildingContext metadataBuildingContext;
 
+    /**
+     * Obtains a mapping object for the given domain class nam
+     *
+     * @param theClass The domain class in question
+     * @return A Mapping object or null
+     */
+    public static Mapping getMapping(Class<?> theClass) {
+        return theClass == null ? null : MAPPING_CACHE.get(theClass);
+    }
+
+    /**
+     * Obtains a mapping object for the given domain class nam
+     *
+     * @param theClass The domain class in question
+     * @return A Mapping object or null
+     */
+    public static Mapping getMappingValidated(Class<?> theClass) {
+        return ofNullable(getMapping(theClass)).orElseThrow();
+    }
+
+    /**
+     * Obtains a mapping object for the given domain class nam
+     *
+     * @param theClass The domain class in question
+     */
+    public static void cacheMapping(Class<?> theClass, Mapping mapping) {
+        MAPPING_CACHE.put(theClass, mapping);
+    }
+
+    /**
+     * Obtains a mapping object for the given domain class nam
+     *
+     * @param domainClass The domain class in question
+     * @return A Mapping object or null
+     */
+    public static Mapping getMapping(PersistentEntity domainClass) {
+        return domainClass == null ? null : 
MAPPING_CACHE.get(domainClass.getJavaClass());
+    }
+
+    public static void clearMappingCache() {
+        MAPPING_CACHE.clear();
+    }
+
+    public static void clearMappingCache(Class<?> theClass) {
+        String className = theClass.getName();
+        for(Iterator<Map.Entry<Class<?>, Mapping>> it = 
MAPPING_CACHE.entrySet().iterator(); it.hasNext();) {
+            Map.Entry<Class<?>, Mapping> entry = it.next();
+            if (className.equals(entry.getKey().getName())) {
+                it.remove();
+            }
+        }
+    }
+
 
     public JdbcEnvironment getJdbcEnvironment() {
         return  
metadataBuildingContext.getMetadataCollector().getDatabase().getJdbcEnvironment();
@@ -212,24 +264,14 @@ public class GrailsDomainBinder
                 , rootMappingDefaults
         );
 
-        
filterHibernateEntities(hibernateMappingContext.getHibernatePersistentEntities())
+        hibernateMappingContext.getHibernatePersistentEntities().stream()
+                .filter(this::isForGrailsDomainMapping)
                 .forEach(hibernatePersistentEntity -> 
bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName));
     }
 
 
-    private List<HibernatePersistentEntity> 
filterHibernateEntities(java.util.Collection<HibernatePersistentEntity> 
persistentEntities) {
-        return persistentEntities.stream()
-                .filter(this::isNotAnnotatedEntity)
-                .filter(this::usesConnectionSource)
-                .filter(HibernatePersistentEntity::isRoot).toList();
-    }
-
-    private boolean usesConnectionSource(HibernatePersistentEntity 
persistentEntity) {
-        return ConnectionSourcesSupport.usesConnectionSource(persistentEntity, 
dataSourceName);
-    }
-
-    private boolean isNotAnnotatedEntity(HibernatePersistentEntity 
persistentEntity) {
-        return 
!persistentEntity.getJavaClass().isAnnotationPresent(Entity.class);
+    private boolean isForGrailsDomainMapping(HibernatePersistentEntity 
persistentEntity) {
+        return persistentEntity.forGrailsDomainMapping(dataSourceName);
     }
 
 
@@ -361,7 +403,7 @@ public class GrailsDomainBinder
             PersistentClass referenced = mappings.getEntityBinding(entityName);
 
             Class<?> mappedClass = referenced.getMappedClass();
-            Mapping m = getMapping(mappedClass);
+            Mapping m = getMappingValidated(mappedClass);
 
             boolean compositeIdProperty = 
m.isCompositeIdProperty(property.getInverseSide());
             if (!compositeIdProperty) {
@@ -439,11 +481,11 @@ public class GrailsDomainBinder
         // Configure one-to-many
         if (collection.isOneToMany()) {
 
-            Mapping m = getRootMapping(referenced);
+            Mapping m = new RootMappingFetcher().getRootMapping(referenced);
             boolean tablePerSubclass = m != null && !m.getTablePerHierarchy();
 
             if (referenced != null && !referenced.isRoot() && 
!tablePerSubclass) {
-                Mapping rootMapping = getRootMapping(referenced);
+                Mapping rootMapping = new 
RootMappingFetcher().getRootMapping(referenced);
                 //TODO FIXME
                 String discriminatorColumnName = 
JPA_DEFAULT_DISCRIMINATOR_TYPE;
 
@@ -496,7 +538,7 @@ public class GrailsDomainBinder
             }
         }
 
-        if (isSorted(property)) {
+        if (property.isSorted()) {
             collection.setSorted(true);
         }
 
@@ -530,7 +572,7 @@ public class GrailsDomainBinder
         }
 
         // if we have a many-to-many
-        if (isManyToMany || isBidirectionalOneToManyMap(property)) {
+        if (isManyToMany || property.isBidirectionalOneToManyMap()) {
             PersistentProperty otherSide = property.getInverseSide();
 
             if (property.isBidirectional()) {
@@ -682,7 +724,7 @@ public class GrailsDomainBinder
                 discriminator = discriminatorConfig.getValue();
             }
         }
-        Mapping rootMapping = getRootMapping(domainClass);
+        Mapping rootMapping = new 
RootMappingFetcher().getRootMapping(domainClass);
         String quote = "'";
         if (rootMapping != null && rootMapping.getDatasources() != null) {
             DiscriminatorConfig discriminatorConfig = 
rootMapping.getDiscriminator();
@@ -698,19 +740,6 @@ public class GrailsDomainBinder
         return theSet;
     }
 
-    private Mapping getRootMapping(PersistentEntity referenced) {
-        return Optional.of(referenced)
-                .map(PersistentEntity::getRootEntity)
-                .filter(HibernatePersistentEntity.class::isInstance)
-                .map(HibernatePersistentEntity.class::cast)
-                .map(HibernatePersistentEntity::getMappedForm)
-                .orElse(null);
-    }
-
-    private boolean isBidirectionalOneToManyMap(Association property) {
-        return Map.class.isAssignableFrom(property.getType()) && 
property.isBidirectional();
-    }
-
     private void bindCollectionWithJoinTable(ToMany property,
                                                InFlightMetadataCollector 
mappings, Collection collection, PropertyConfig config, String 
sessionFactoryBeanName) {
 
@@ -947,16 +976,6 @@ public class GrailsDomainBinder
         }
     }
 
-    /**
-     * Establish whether a collection property is sorted
-     *
-     * @param property The property
-     * @return true if sorted
-     */
-    private boolean isSorted(PersistentProperty property) {
-        return SortedSet.class.isAssignableFrom(property.getType());
-    }
-
     /**
      * Binds a many-to-many relationship. A many-to-many consists of
      * - a key (a DependentValue)
@@ -1100,13 +1119,6 @@ public class GrailsDomainBinder
     }
 
 
-    private PhysicalNamingStrategy getPhysicalNamingStrategy(String 
sessionFactoryBeanName) {
-        return 
NAMING_STRATEGY_PROVIDER.getPhysicalNamingStrategy(sessionFactoryBeanName);
-    }
-
-
-
-
     /**
      * Binds a Grails domain class to the Hibernate runtime meta model
      *
@@ -1128,7 +1140,7 @@ public class GrailsDomainBinder
             try {
                 final Mapping m = new 
HibernateEntityWrapper().getMappedForm(domainClass);
                 trackCustomCascadingSaves(m, 
domainClass.getPersistentProperties());
-                
AbstractGrailsDomainBinder.cacheMapping(domainClass.getJavaClass(), m);
+                cacheMapping(domainClass.getJavaClass(), m);
             } catch (Exception e) {
                 throw new DatastoreConfigurationException("Error evaluating 
ORM mappings block for domain [" +
                         domainClass.getName() + "]:  " + e.getMessage(), e);
@@ -1147,38 +1159,11 @@ public class GrailsDomainBinder
             PropertyConfig propConf = 
mapping.getPropertyConfig(property.getName());
 
             if (propConf != null && propConf.getCascade() != null) {
-                
propConf.setExplicitSaveUpdateCascade(isSaveUpdateCascade(propConf.getCascade()));
+                
propConf.setExplicitSaveUpdateCascade(CascadeBehavior.isSaveUpdate(propConf.getCascade()));
             }
         }
     }
 
-    /**
-     * Check if a save-update cascade is defined within the Hibernate cascade 
properties string.
-     * @param cascade The string containing the cascade properties.
-     * @return True if save-update or any other cascade property that 
encompasses those is present.
-     */
-    protected boolean isSaveUpdateCascade(String cascade) {
-        return CascadeBehavior.isSaveUpdate(cascade);
-    }
-
-    /**
-     * Obtains a mapping object for the given domain class nam
-     *
-     * @param theClass The domain class in question
-     * @return A Mapping object or null
-     */
-    private static Mapping getMapping(Class<?> theClass) {
-        return 
Optional.ofNullable(AbstractGrailsDomainBinder.getMapping(theClass)).orElseThrow();
-    }
-
-
-    public static void clearMappingCache() {
-        AbstractGrailsDomainBinder.clearMappingCache();
-    }
-
-    public static void clearMappingCache(Class<?> theClass) {
-        // no-op, here for compatibility
-    }
 
     /**
      * Binds a root class (one with no super classes) to the runtime meta model
@@ -1223,7 +1208,7 @@ public class GrailsDomainBinder
 
     public PersistentEntityNamingStrategy getNamingStrategy() {
         if (namingStrategy == null) {
-            namingStrategy = new 
NamingStrategyWrapper(getPhysicalNamingStrategy(sessionFactoryName), 
getJdbcEnvironment());
+            namingStrategy = new 
NamingStrategyWrapper(NAMING_STRATEGY_PROVIDER.getPhysicalNamingStrategy(sessionFactoryName),
 getJdbcEnvironment());
         }
         return namingStrategy;
     }
@@ -1285,7 +1270,7 @@ public class GrailsDomainBinder
                 .stream()
                 .filter(HibernatePersistentEntity.class::isInstance)
                 .map(HibernatePersistentEntity.class::cast)
-                .filter(this::usesConnectionSource)
+                .filter(persistentEntity -> 
persistentEntity.usesConnectionSource(dataSourceName))
                 .filter(sub -> isChildEntity(sub, domainClass))
                 .forEach( sub -> bindSubClass(sub, parent, mappings, 
sessionFactoryBeanName, m));
 
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java
index 29b07d9527..af8b90e4e6 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java
@@ -15,10 +15,13 @@
  */
 package org.grails.orm.hibernate.cfg;
 
+import org.grails.datastore.mapping.core.connections.ConnectionSourcesSupport;
 import org.grails.datastore.mapping.model.*;
 
 import java.util.Optional;
 
+import jakarta.persistence.Entity;
+
 /**
  * Persistent entity implementation for Hibernate
  *
@@ -63,4 +66,20 @@ public class HibernatePersistentEntity extends 
AbstractPersistentEntity<Mapping>
     public Mapping getMappedForm() {
         return 
Optional.ofNullable(getMapping()).map(ClassMapping::getMappedForm).orElse(null);
     }
+
+    private boolean isAnnotatedEntity() {
+        return getJavaClass().isAnnotationPresent(Entity.class);
+    }
+
+    public boolean usesConnectionSource(String dataSourceName) {
+        return ConnectionSourcesSupport.usesConnectionSource(this, 
dataSourceName);
+    }
+
+    public boolean forGrailsDomainMapping(String dataSourceName) {
+        return !isAnnotatedEntity()
+                && usesConnectionSource(dataSourceName)
+                && isRoot();
+    }
+
+
 }
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/RootMappingFetcher.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/RootMappingFetcher.java
new file mode 100644
index 0000000000..741e590689
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/RootMappingFetcher.java
@@ -0,0 +1,19 @@
+package org.grails.orm.hibernate.cfg.domainbinding;
+
+import java.util.Optional;
+
+import org.grails.datastore.mapping.model.PersistentEntity;
+import org.grails.orm.hibernate.cfg.HibernatePersistentEntity;
+import org.grails.orm.hibernate.cfg.Mapping;
+
+public class RootMappingFetcher {
+
+    public Mapping getRootMapping(PersistentEntity referenced) {
+        return Optional.ofNullable(referenced)
+                .map(PersistentEntity::getRootEntity)
+                .filter(HibernatePersistentEntity.class::isInstance)
+                .map(HibernatePersistentEntity.class::cast)
+                .map(HibernatePersistentEntity::getMappedForm)
+                .orElse(null);
+    }
+}
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/GrailsHibernateQueryUtils.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/GrailsHibernateQueryUtils.java
index 88fb486460..8195a0e9c0 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/GrailsHibernateQueryUtils.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/GrailsHibernateQueryUtils.java
@@ -2,7 +2,7 @@ package org.grails.orm.hibernate.query;
 
 import org.grails.datastore.mapping.config.Property;
 import org.grails.datastore.mapping.reflect.ClassUtils;
-import org.grails.orm.hibernate.cfg.AbstractGrailsDomainBinder;
+import org.grails.orm.hibernate.cfg.GrailsDomainBinder;
 import org.grails.orm.hibernate.cfg.Mapping;
 import org.grails.datastore.gorm.finders.DynamicFinder;
 import org.grails.datastore.mapping.model.PersistentEntity;
@@ -11,23 +11,20 @@ import org.grails.datastore.mapping.model.types.Association;
 import org.grails.datastore.mapping.model.types.Embedded;
 import org.hibernate.FetchMode;
 import org.hibernate.FlushMode;
-import org.hibernate.LockMode;
 import org.hibernate.query.Query;
 import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
 import org.hibernate.query.sqm.tree.expression.SqmFunction;
 
 import org.springframework.core.convert.ConversionService;
-import org.springframework.util.ReflectionUtils;
 
 import jakarta.persistence.LockModeType;
 import jakarta.persistence.criteria.*;
-import java.lang.reflect.Method;
+
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.stream.Stream;
 
 
 /**
@@ -89,7 +86,7 @@ public class GrailsHibernateQueryUtils {
                 addOrderPossiblyNested(query, queryRoot, 
criteriaBuilder,entity, sort, order, ignoreCase);
             }
         } else if (useDefaultMapping) {
-            Mapping m = 
AbstractGrailsDomainBinder.getMapping(entity.getJavaClass());
+            Mapping m = GrailsDomainBinder.getMapping(entity.getJavaClass());
             if (m != null) {
                 Map sortMap = m.getSort().getNamesAndDirections();
                 for (Object sort : sortMap.keySet()) {
@@ -281,7 +278,7 @@ public class GrailsHibernateQueryUtils {
      * @param criteria    The criteria
      */
     private static void cacheCriteriaByMapping(Class<?> targetClass, Query 
criteria) {
-        Mapping m = AbstractGrailsDomainBinder.getMapping(targetClass);
+        Mapping m = GrailsDomainBinder.getMapping(targetClass);
         if (m != null && m.getCache() != null && m.getCache().getEnabled()) {
             criteria.setCacheable(true);
         }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/proxy/Hibernate7GroovyProxySpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/proxy/Hibernate7GroovyProxySpec.groovy
index 59c59c1145..21f070c6d4 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/proxy/Hibernate7GroovyProxySpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/proxy/Hibernate7GroovyProxySpec.groovy
@@ -28,6 +28,7 @@ class Hibernate7GroovyProxySpec extends 
GrailsDataTckSpec<GrailsDataHibernate7Tc
         id == location.id
         // Use the method on the proxy
         false == location.isInitialized()
+        false == org.hibernate.Hibernate.isInitialized(location)
 
         "UK" == location.code
         "United Kingdom - UK" == location.namedAndCode()
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/RootMappingFetcherSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/RootMappingFetcherSpec.groovy
new file mode 100644
index 0000000000..1c55b84786
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/RootMappingFetcherSpec.groovy
@@ -0,0 +1,60 @@
+package org.grails.orm.hibernate.cfg.domainbinding
+
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.orm.hibernate.cfg.HibernatePersistentEntity
+import org.grails.orm.hibernate.cfg.Mapping
+import spock.lang.Specification
+
+class RootMappingFetcherSpec extends Specification {
+
+    RootMappingFetcher fetcher = new RootMappingFetcher()
+
+    void "test getRootMapping returns null for null input"() {
+        expect:
+        fetcher.getRootMapping(null) == null
+    }
+
+    void "test getRootMapping returns null for 
non-HibernatePersistentEntity"() {
+        given:
+        PersistentEntity entity = Mock(PersistentEntity)
+        entity.getRootEntity() >> entity
+
+        expect:
+        fetcher.getRootMapping(entity) == null
+    }
+
+    void "test getRootMapping returns mapping for HibernatePersistentEntity"() 
{
+        given:
+        HibernatePersistentEntity entity = Mock(HibernatePersistentEntity)
+        Mapping mapping = new Mapping()
+        entity.getRootEntity() >> entity
+        entity.getMappedForm() >> mapping
+
+        expect:
+        fetcher.getRootMapping(entity) == mapping
+    }
+
+    void "test getRootMapping returns root mapping for child 
HibernatePersistentEntity"() {
+        given:
+        HibernatePersistentEntity root = Mock(HibernatePersistentEntity)
+        HibernatePersistentEntity child = Mock(HibernatePersistentEntity)
+        Mapping rootMapping = new Mapping()
+        
+        child.getRootEntity() >> root
+        root.getMappedForm() >> rootMapping
+
+        expect:
+        fetcher.getRootMapping(child) == rootMapping
+    }
+
+    void "test getRootMapping returns null if root is not 
HibernatePersistentEntity"() {
+        given:
+        HibernatePersistentEntity child = Mock(HibernatePersistentEntity)
+        PersistentEntity root = Mock(PersistentEntity)
+
+        child.getRootEntity() >> root
+
+        expect:
+        fetcher.getRootMapping(child) == null
+    }
+}
diff --git 
a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java
 
b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java
index d6f3a30551..5f9c35fce3 100644
--- 
a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java
+++ 
b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java
@@ -30,6 +30,7 @@ import org.grails.datastore.mapping.model.types.ToOne;
 import org.grails.datastore.mapping.reflect.EntityReflector;
 
 import java.util.Optional;
+import java.util.SortedSet;
 
 import static java.util.Optional.ofNullable;
 
@@ -120,4 +121,14 @@ public interface PersistentProperty<T extends Property> {
         return this instanceof ManyToMany || isUnidirectionalOneToMany()|| 
this instanceof Basic;
     }
 
+    /**
+     * Establish whether a collection property is sorted
+     *
+     * @param property The property
+     * @return true if sorted
+     */
+    default boolean isSorted() {
+        return SortedSet.class.isAssignableFrom(this.getType());
+    }
+
 }
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 98b226f6e4..0778786d0b 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,4 +369,8 @@ public abstract class Association<T extends Property> 
extends AbstractPersistent
                 .map(otherSide -> !this.isOwningSide() && 
otherSide.isOwningSide())
                 .orElse(false);
     }
+
+    public boolean isBidirectionalOneToManyMap() {
+        return Map.class.isAssignableFrom(this.getType()) && 
this.isBidirectional();
+    }
 }

Reply via email to