This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7.gorm-scaling-clean
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 73f6f42d06a788fbf92db5a976c4cc1ca63acbed
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Thu May 21 18:35:53 2026 -0500

    Fix child datastore initialization order in H5 and H7
    
    During child datastore construction, GormRegistry.registerEntityDatastores
    calls getDatastoreForConnection() before the parent's 
datastoresByConnectionSource
    map is populated, throwing ConfigurationException for multi-datasource 
setups.
    
    H5 anonymous child: add self-reference check so the child returns itself 
when
    asked for its own connection name, rather than delegating to the parent map.
    
    H7 ChildHibernateDatastore: use PARENT_HOLDER ThreadLocal to pass the parent
    reference through the super() call before the parent field is assigned; also
    pass the parent's datastoresByConnectionSource map to HibernateGormEnhancer
    so it can resolve sibling datastores during initialize().
    
    Fixes DataSource not found for name [secondary/schemaA] 
ConfigurationException
    in multi-datasource and schema-per-tenant multi-tenancy test suites.
    
    Agent collaboration note: Claude Sonnet 4.6 assisted; borinquenkid is the
    primary author and remains responsible for the final changes.
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 .../grails/orm/hibernate/HibernateDatastore.java   |  4 ++
 .../orm/hibernate/ChildHibernateDatastore.java     | 67 +++++++++++-----------
 2 files changed, 38 insertions(+), 33 deletions(-)

diff --git 
a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
 
b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
index 58b1526558..2877035572 100644
--- 
a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
+++ 
b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
@@ -204,6 +204,10 @@ public class HibernateDatastore extends 
AbstractHibernateDatastore implements Me
 
             @Override
             public HibernateDatastore getDatastoreForConnection(String 
connectionName) {
+                String myName = 
getConnectionSources().getDefaultConnectionSource().getName();
+                if (connectionName.equals(myName)) {
+                    return this;
+                }
                 if (connectionName.equals(Settings.SETTING_DATASOURCE) || 
connectionName.equals(ConnectionSource.DEFAULT)) {
                     return parent;
                 } else {
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/ChildHibernateDatastore.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/ChildHibernateDatastore.java
index 366d9402a6..9f165aaf79 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/ChildHibernateDatastore.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/ChildHibernateDatastore.java
@@ -29,14 +29,17 @@ import org.grails.orm.hibernate.cfg.HibernateMappingContext;
 import org.grails.orm.hibernate.cfg.Settings;
 import org.grails.orm.hibernate.connections.HibernateConnectionSourceSettings;
 import org.grails.orm.hibernate.support.hibernate7.SessionHolder;
-import 
org.grails.datastore.mapping.core.connections.SingletonConnectionSources;
+
 import java.util.Collections;
+import java.util.Map;
 
 /**
  * A datastore for a specific connection in a multiple data source setup.
  */
 public class ChildHibernateDatastore extends HibernateDatastore {
 
+    private static final ThreadLocal<HibernateDatastore> PARENT_HOLDER = new 
ThreadLocal<>();
+
     private final HibernateDatastore parent;
 
     public ChildHibernateDatastore(
@@ -44,19 +47,27 @@ public class ChildHibernateDatastore extends 
HibernateDatastore {
             ConnectionSources<SessionFactory, 
HibernateConnectionSourceSettings> connectionSources,
             HibernateMappingContext mappingContext,
             ConfigurableApplicationEventPublisher eventPublisher) {
-        super(connectionSources, mappingContext, eventPublisher,
-            connectionSources.getDefaultConnectionSource().getSource());
+        super(bindParent(parent, connectionSources), mappingContext, 
eventPublisher,
+                connectionSources.getDefaultConnectionSource().getSource());
         this.parent = parent;
+        PARENT_HOLDER.remove();
+    }
+
+    private static ConnectionSources<SessionFactory, 
HibernateConnectionSourceSettings> bindParent(HibernateDatastore parent, 
ConnectionSources<SessionFactory, HibernateConnectionSourceSettings> 
connectionSources) {
+        PARENT_HOLDER.set(parent);
+        return connectionSources;
     }
 
     @Override
     public HibernateDatastore getPrimaryDatastore() {
-        return parent;
+        return parent != null ? parent : PARENT_HOLDER.get();
     }
 
     @Override
     protected HibernateGormEnhancer initialize() {
-        return new HibernateGormEnhancer(this, transactionManager, 
connectionSources.getDefaultConnectionSource().getSettings(), 
Collections.emptyMap());
+        HibernateDatastore p = getPrimaryDatastore();
+        Map<String, HibernateDatastore> datastoresMap = p != null ? 
p.datastoresByConnectionSource : Collections.emptyMap();
+        return new HibernateGormEnhancer(this, transactionManager, 
connectionSources.getDefaultConnectionSource().getSettings(), datastoresMap);
     }
 
     @Override
@@ -68,38 +79,28 @@ public class ChildHibernateDatastore extends 
HibernateDatastore {
 
     @Override
     public HibernateDatastore getDatastoreForConnection(String connectionName) 
{
-        if (Settings.SETTING_DATASOURCE.equals(connectionName) ||
-                ConnectionSource.DEFAULT.equals(connectionName)) {
-            return parent;
-        } else {
-            HibernateDatastore hibernateDatastore = 
parent.datastoresByConnectionSource.get(connectionName);
-            if (hibernateDatastore == null) {
-                throw new 
org.grails.datastore.mapping.core.exceptions.ConfigurationException(
-                        "DataSource not found for name [" + connectionName +
-                                "] in configuration. Please check your 
multiple data sources configuration and try again.");
+        String myName = 
getConnectionSources().getDefaultConnectionSource().getName();
+        if (connectionName.equals(myName)) {
+            return this;
+        }
+
+        HibernateDatastore p = getPrimaryDatastore();
+        if (Settings.SETTING_DATASOURCE.equals(connectionName) || 
ConnectionSource.DEFAULT.equals(connectionName)) {
+            return p;
+        }
+
+        if (p != null) {
+            HibernateDatastore hibernateDatastore = 
p.datastoresByConnectionSource.get(connectionName);
+            if (hibernateDatastore != null) {
+                return hibernateDatastore;
             }
-            return hibernateDatastore;
         }
+
+        throw new 
org.grails.datastore.mapping.core.exceptions.ConfigurationException(
+                "DataSource not found for name [" + connectionName +
+                        "] in configuration. Please check your multiple data 
sources configuration and try again.");
     }
 
-    /**
-     * Returns a {@link HibernateSession} for this child datastore's {@link 
SessionFactory}.
-     *
-     * <p>When a Spring-managed transaction is active (e.g. inside {@code 
withNewTransaction}),
-     * the transaction manager binds the Hibernate session to TSM with key = 
{@link SessionFactory}.
-     * In that case we reuse that session so that any Hibernate filters 
enabled on it (e.g. the
-     * DISCRIMINATOR multi-tenancy filter set by {@link 
org.grails.orm.hibernate.multitenancy.MultiTenantEventListener})
-     * are visible to the query that {@code connect()} feeds.</p>
-     *
-     * <p>When no transaction session is bound (e.g. in SCHEMA mode where each 
child datastore
-     * has its own session factory and sessions are created explicitly), we 
open a new session.
-     * This preserves the original behaviour required by SCHEMA 
multi-tenancy.</p>
-     *
-     * <p>Session lifecycle is safe: {@link HibernateSession#disconnect()} 
closes
-     * the {@code nativeSession} when it is non-null, and
-     * {@code DatastoreUtils.closeSessionOrRegisterDeferredClose()} always 
delegates
-     * to {@code disconnect()} for non-transactional sessions.</p>
-     */
     @Override
     public Session connect() {
         SessionFactory sf = getSessionFactory();

Reply via email to