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/15 20:48:32 UTC
(grails-core) 03/24: Refactor nested datastore classes and fix lifecycle issues in multitenancy
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 e6f6e2462ef3817c5107394dc28dee3d826b4d76
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Apr 12 09:36:18 2026 -0500
Refactor nested datastore classes and fix lifecycle issues in multitenancy
---
.../orm/hibernate/ChildHibernateDatastore.java | 74 +++++++++++++
.../grails/orm/hibernate/HibernateDatastore.java | 119 +++------------------
.../orm/hibernate/SchemaTenantGormEnhancer.java | 94 ++++++++++++++++
.../ChildHibernateDatastoreUnitSpec.groovy | 2 +-
4 files changed, 181 insertions(+), 108 deletions(-)
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
new file mode 100644
index 0000000000..f526518320
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/ChildHibernateDatastore.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import org.grails.datastore.mapping.core.connections.ConnectionSource;
+import org.grails.datastore.mapping.core.connections.ConnectionSources;
+import org.grails.orm.hibernate.cfg.HibernateMappingContext;
+import org.grails.orm.hibernate.cfg.Settings;
+import org.hibernate.SessionFactory;
+import org.grails.datastore.gorm.events.ConfigurableApplicationEventPublisher;
+import org.grails.orm.hibernate.connections.HibernateConnectionSourceSettings;
+
+/**
+ * A datastore for a specific connection in a multiple data source setup.
+ */
+public class ChildHibernateDatastore extends HibernateDatastore {
+
+ private final HibernateDatastore parent;
+
+ public ChildHibernateDatastore(
+ HibernateDatastore parent,
+ ConnectionSources<SessionFactory, HibernateConnectionSourceSettings> connectionSources,
+ HibernateMappingContext mappingContext,
+ ConfigurableApplicationEventPublisher eventPublisher) {
+ super(connectionSources, mappingContext, eventPublisher,
+ connectionSources.getDefaultConnectionSource().getSource());
+ this.parent = parent;
+ }
+
+ @Override
+ protected HibernateGormEnhancer initialize() {
+ return null;
+ }
+
+ @Override
+ public void destroy() {
+ if (!this.destroyed) {
+ // Only mark as destroyed, don't close shared resources
+ this.destroyed = true;
+ }
+ }
+
+ @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.");
+ }
+ return hibernateDatastore;
+ }
+ }
+}
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
index 5326bf8b14..b99cdd5dd5 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.java
@@ -158,9 +158,8 @@ public class HibernateDatastore extends AbstractDatastore
HibernateQueryArgument.CONFIG_PASS_READONLY.value();
/** The session factory. */
- private static final String INFORMATION_SCHEMA = "INFORMATION_SCHEMA";
-
- private static final String PUBLIC_SCHEMA = "PUBLIC";
+ public static final String INFORMATION_SCHEMA = "INFORMATION_SCHEMA";
+ public static final String PUBLIC_SCHEMA = "PUBLIC";
protected SessionFactory sessionFactory;
@@ -203,7 +202,7 @@ public class HibernateDatastore extends AbstractDatastore
/** The tenant resolver. */
protected final TenantResolver tenantResolver;
- private boolean destroyed;
+ protected boolean destroyed;
protected final GrailsHibernateTransactionManager transactionManager;
protected final ConfigurableApplicationEventPublisher eventPublisher;
@@ -226,10 +225,10 @@ public class HibernateDatastore extends AbstractDatastore
this(connectionSources, mappingContext, eventPublisher, null);
}
- private HibernateDatastore(
- final ConnectionSources<SessionFactory, HibernateConnectionSourceSettings> connectionSources,
- final HibernateMappingContext mappingContext,
- final ConfigurableApplicationEventPublisher eventPublisher,
+ protected HibernateDatastore(
+ ConnectionSources<SessionFactory, HibernateConnectionSourceSettings> connectionSources,
+ HibernateMappingContext mappingContext,
+ ConfigurableApplicationEventPublisher eventPublisher,
SessionFactory sessionFactory) {
super(mappingContext, connectionSources.getBaseConfiguration(), null);
this.connectionSources = connectionSources;
@@ -639,6 +638,11 @@ public class HibernateDatastore extends AbstractDatastore
public void destroy() {
if (!this.destroyed) {
try {
+ for (HibernateDatastore childDatastore : datastoresByConnectionSource.values()) {
+ if (childDatastore != this && childDatastore.getMappingContext() != getMappingContext()) {
+ childDatastore.destroy();
+ }
+ }
super.destroy();
HibernateGormInstanceApi.resetInsertActive();
try {
@@ -994,103 +998,4 @@ public class HibernateDatastore extends AbstractDatastore
}
}
}
-
- /**
- * A {@link HibernateGormEnhancer} for SCHEMA multi-tenancy mode that resolves all tenant qualifiers
- * from either the registered {@link AllTenantsResolver} or the available schema names on the data source.
- */
- public static class SchemaTenantGormEnhancer extends HibernateGormEnhancer {
-
- private final HibernateConnectionSource defaultConnectionSource;
- private final TenantResolver tenantResolver;
- private final SchemaHandler schemaHandler;
- private final Map<String, HibernateDatastore> datastoresByConnectionSource;
-
- public SchemaTenantGormEnhancer(
- Datastore datastore,
- PlatformTransactionManager transactionManager,
- HibernateConnectionSource defaultConnectionSource,
- TenantResolver tenantResolver,
- SchemaHandler schemaHandler,
- Map<String, HibernateDatastore> datastoresByConnectionSource) {
- super(datastore, transactionManager, defaultConnectionSource.getSettings());
- this.defaultConnectionSource = defaultConnectionSource;
- this.tenantResolver = tenantResolver;
- this.schemaHandler = schemaHandler;
- this.datastoresByConnectionSource = datastoresByConnectionSource;
- // super() calls registerEntity → allQualifiers before our fields are set.
- // Re-register now that all fields are initialized so schema qualifiers are wired correctly.
- for (PersistentEntity entity : datastore.getMappingContext().getPersistentEntities()) {
- registerEntity(entity);
- }
- }
-
- @Override
- public List<String> allQualifiers(Datastore datastore, PersistentEntity entity) {
- List<String> allQualifiers = super.allQualifiers(datastore, entity);
- // Guard against being called from super() before our fields are initialized.
- if (defaultConnectionSource == null) {
- return allQualifiers;
- }
- if (MultiTenant.class.isAssignableFrom(entity.getJavaClass())) {
- if (tenantResolver instanceof AllTenantsResolver allTenantsResolver) {
- Iterable<Serializable> tenantIds = allTenantsResolver.resolveTenantIds();
- for (Serializable id : tenantIds) {
- allQualifiers.add(id.toString());
- }
- } else {
- Collection<String> schemaNames =
- schemaHandler.resolveSchemaNames(defaultConnectionSource.getDataSource());
- for (String schemaName : schemaNames) {
- if (INFORMATION_SCHEMA.equals(schemaName) || PUBLIC_SCHEMA.equals(schemaName)) continue;
- for (String connectionName : datastoresByConnectionSource.keySet()) {
- if (schemaName.equalsIgnoreCase(connectionName)) {
- allQualifiers.add(connectionName);
- }
- }
- }
- }
- }
- return allQualifiers;
- }
- }
-
- /**
- * A datastore for a specific connection in a multiple data source setup.
- */
- public static class ChildHibernateDatastore extends HibernateDatastore {
-
- private final HibernateDatastore parent;
-
- public ChildHibernateDatastore(
- HibernateDatastore parent,
- ConnectionSources<SessionFactory, HibernateConnectionSourceSettings> connectionSources,
- HibernateMappingContext mappingContext,
- ConfigurableApplicationEventPublisher eventPublisher) {
- super(connectionSources, mappingContext, eventPublisher,
- connectionSources.getDefaultConnectionSource().getSource());
- this.parent = parent;
- }
-
- @Override
- protected HibernateGormEnhancer initialize() {
- return null;
- }
-
- @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 ConfigurationException(
- "DataSource not found for name [" + connectionName +
- "] in configuration. Please check your multiple data sources configuration and try again.");
- }
- return hibernateDatastore;
- }
- }
- }
}
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/SchemaTenantGormEnhancer.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/SchemaTenantGormEnhancer.java
new file mode 100644
index 0000000000..be412f3c99
--- /dev/null
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/SchemaTenantGormEnhancer.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import grails.gorm.MultiTenant;
+import org.grails.datastore.mapping.core.Datastore;
+import org.grails.datastore.mapping.multitenancy.AllTenantsResolver;
+import org.grails.datastore.mapping.multitenancy.TenantResolver;
+import org.grails.datastore.mapping.model.PersistentEntity;
+import org.grails.datastore.gorm.jdbc.schema.SchemaHandler;
+import org.grails.orm.hibernate.connections.HibernateConnectionSource;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link HibernateGormEnhancer} for SCHEMA multi-tenancy mode that resolves all tenant qualifiers
+ * from either the registered {@link AllTenantsResolver} or the available schema names on the data source.
+ */
+public class SchemaTenantGormEnhancer extends HibernateGormEnhancer {
+
+ private final HibernateConnectionSource defaultConnectionSource;
+ private final TenantResolver tenantResolver;
+ private final SchemaHandler schemaHandler;
+ private final Map<String, HibernateDatastore> datastoresByConnectionSource;
+
+ public SchemaTenantGormEnhancer(
+ Datastore datastore,
+ PlatformTransactionManager transactionManager,
+ HibernateConnectionSource defaultConnectionSource,
+ TenantResolver tenantResolver,
+ SchemaHandler schemaHandler,
+ Map<String, HibernateDatastore> datastoresByConnectionSource) {
+ super(datastore, transactionManager, defaultConnectionSource.getSettings());
+ this.defaultConnectionSource = defaultConnectionSource;
+ this.tenantResolver = tenantResolver;
+ this.schemaHandler = schemaHandler;
+ this.datastoresByConnectionSource = datastoresByConnectionSource;
+ // super() calls registerEntity → allQualifiers before our fields are set.
+ // Re-register now that all fields are initialized so schema qualifiers are wired correctly.
+ for (PersistentEntity entity : datastore.getMappingContext().getPersistentEntities()) {
+ registerEntity(entity);
+ }
+ }
+
+ @Override
+ public List<String> allQualifiers(Datastore datastore, PersistentEntity entity) {
+ List<String> allQualifiers = super.allQualifiers(datastore, entity);
+ // Guard against being called from super() before our fields are initialized.
+ if (defaultConnectionSource == null) {
+ return allQualifiers;
+ }
+ if (MultiTenant.class.isAssignableFrom(entity.getJavaClass())) {
+ if (tenantResolver instanceof AllTenantsResolver allTenantsResolver) {
+ Iterable<Serializable> tenantIds = allTenantsResolver.resolveTenantIds();
+ for (Serializable id : tenantIds) {
+ allQualifiers.add(id.toString());
+ }
+ } else {
+ Collection<String> schemaNames =
+ schemaHandler.resolveSchemaNames(defaultConnectionSource.getDataSource());
+ for (String schemaName : schemaNames) {
+ if (HibernateDatastore.INFORMATION_SCHEMA.equals(schemaName) || HibernateDatastore.PUBLIC_SCHEMA.equals(schemaName)) continue;
+ for (String connectionName : datastoresByConnectionSource.keySet()) {
+ if (schemaName.equalsIgnoreCase(connectionName)) {
+ allQualifiers.add(connectionName);
+ }
+ }
+ }
+ }
+ }
+ return allQualifiers;
+ }
+}
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/ChildHibernateDatastoreUnitSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/ChildHibernateDatastoreUnitSpec.groovy
index e7719b6ec3..bf1f4a57fb 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/ChildHibernateDatastoreUnitSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/ChildHibernateDatastoreUnitSpec.groovy
@@ -48,7 +48,7 @@ class ChildHibernateDatastoreUnitSpec extends HibernateGormDatastoreSpec {
def secondaryConnectionSource = factory.create("secondary", dataSourceConnectionSource, settings)
when: "A child datastore is created"
- def child = new HibernateDatastore.ChildHibernateDatastore(
+ def child = new ChildHibernateDatastore(
parent,
new SingletonConnectionSources(secondaryConnectionSource, parent.connectionSources.getBaseConfiguration()),
parent.mappingContext,