You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/06/26 19:57:39 UTC
(grails-core) branch fix/gorm-api-registration-scaling created (now 2498f91f60)
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a change to branch fix/gorm-api-registration-scaling
in repository https://gitbox.apache.org/repos/asf/grails-core.git
at 2498f91f60 Cover Hibernate GORM API allocation combinations
This branch includes the following new commits:
new a6ddce0c16 Reduce GORM API allocation for tenant qualifiers
new 2498f91f60 Cover Hibernate GORM API allocation combinations
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(grails-core) 01/02: Reduce GORM API allocation for tenant qualifiers
Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch fix/gorm-api-registration-scaling
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit a6ddce0c16f1b15f8621385369ef4adaf2231f22
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Fri Jun 26 15:29:04 2026 -0400
Reduce GORM API allocation for tenant qualifiers
Create datastore routing entries for every expanded qualifier, but only
eagerly allocate GORM API providers for the canonical qualifier set.
When a deferred qualifier is used, create and cache its static, instance,
and validation APIs through the owning enhancer so schema and datasource
routing continue to use the requested qualifier.
Assisted-by: Hephaestus:openai/gpt-5.5
---
.../org/grails/datastore/gorm/GormEnhancer.groovy | 108 +++++++++++++++--
.../gorm/GormEnhancerAllQualifiersSpec.groovy | 131 ++++++++++++++++++++-
2 files changed, 226 insertions(+), 13 deletions(-)
diff --git a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormEnhancer.groovy b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormEnhancer.groovy
index 3b1d2348e0..7cbe072a13 100644
--- a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormEnhancer.groovy
+++ b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormEnhancer.groovy
@@ -79,6 +79,8 @@ class GormEnhancer implements Closeable {
return new ConcurrentHashMap() as Map<String, Datastore>
}
+ private static final Map<Datastore, GormEnhancer> ENHANCERS = new ConcurrentHashMap<>()
+
private static final Map<Class, Datastore> DATASTORES_BY_TYPE = new ConcurrentHashMap<Class, Datastore>()
final Datastore datastore
@@ -122,6 +124,7 @@ class GormEnhancer implements Closeable {
registerConstraints(datastore)
}
NAMED_QUERIES.clear()
+ ENHANCERS.put(datastore, this)
DATASTORES_BY_TYPE.put(datastore.getClass(), datastore)
for (entity in datastore.mappingContext.persistentEntities) {
@@ -140,10 +143,12 @@ class GormEnhancer implements Closeable {
def cls = entity.javaClass
List<String> qualifiers = allQualifiers(this.datastore, entity)
- if (!qualifiers.contains(ConnectionSource.DEFAULT)) {
- def firstQualifier = qualifiers.first()
+ List<String> apiQualifiers = apiQualifiers(entity, qualifiers)
+ def name = entity.name
+
+ if (!apiQualifiers.contains(ConnectionSource.DEFAULT)) {
+ def firstQualifier = apiQualifiers.first()
def staticApi = getStaticApi(cls, firstQualifier)
- def name = entity.name
STATIC_APIS.get(ConnectionSource.DEFAULT).put(name, staticApi)
def instanceApi = getInstanceApi(cls, firstQualifier)
INSTANCE_APIS.get(ConnectionSource.DEFAULT).put(name, instanceApi)
@@ -152,19 +157,45 @@ class GormEnhancer implements Closeable {
DATASTORES.get(ConnectionSource.DEFAULT).put(name, this.datastore)
}
- for (qualifier in qualifiers) {
+ for (qualifier in apiQualifiers) {
def staticApi = getStaticApi(cls, qualifier)
- def name = entity.name
STATIC_APIS.get(qualifier).put(name, staticApi)
def instanceApi = getInstanceApi(cls, qualifier)
INSTANCE_APIS.get(qualifier).put(name, instanceApi)
def validationApi = getValidationApi(cls, qualifier)
VALIDATION_APIS.get(qualifier).put(name, validationApi)
+ }
+ for (qualifier in qualifiers) {
DATASTORES.get(qualifier).put(name, this.datastore)
}
}
}
+ /**
+ * Obtain the qualifiers that need eagerly-created API providers.
+ *
+ * Entities expanded to every connection source or tenant still need datastore routing entries
+ * for those qualifiers, but the expensive API providers can be created lazily when a qualifier
+ * is actually used.
+ *
+ * @param entity The persistent entity
+ * @param qualifiers All datastore qualifiers for the entity
+ * @return The qualifiers to eagerly initialize API providers for
+ */
+ protected List<String> apiQualifiers(PersistentEntity entity, List<String> qualifiers) {
+ List<String> configuredQualifiers = new ArrayList<>(ConnectionSourcesSupport.getConnectionSourceNames(entity))
+ if (configuredQualifiers.contains(ConnectionSource.ALL)) {
+ return [ConnectionSource.DEFAULT]
+ }
+
+ boolean isMultiTenant = MultiTenant.isAssignableFrom(entity.javaClass)
+ if (isMultiTenant && configuredQualifiers.equals(ConnectionSourcesSupport.DEFAULT_CONNECTION_SOURCE_NAMES)) {
+ return [ConnectionSource.DEFAULT]
+ }
+
+ return qualifiers
+ }
+
/**
* Obtain all of the qualifiers (typically the connection names) for the datastore and entity
*
@@ -245,12 +276,29 @@ class GormEnhancer implements Closeable {
static <D> GormStaticApi<D> findStaticApi(Class<D> entity, String qualifier = findTenantId(entity)) {
String className = NameUtils.getClassName(entity)
def staticApi = STATIC_APIS.get(qualifier)?.get(className)
+ if (staticApi == null) {
+ staticApi = initializeStaticApi(entity, qualifier, className)
+ }
if (staticApi == null) {
throw stateException(entity)
}
return staticApi
}
+ private static <D> GormStaticApi<D> initializeStaticApi(Class<D> entity, String qualifier, String className) {
+ GormEnhancer enhancer = findEnhancer(entity, qualifier, className)
+ if (enhancer == null) {
+ return null
+ }
+ GormStaticApi<D> staticApi = STATIC_APIS.get(qualifier)?.get(className)
+ if (staticApi != null) {
+ return staticApi
+ }
+ staticApi = enhancer.getStaticApi(entity, qualifier)
+ STATIC_APIS.get(qualifier).put(className, staticApi)
+ return staticApi
+ }
+
/**
* Find an instance API for the give entity type and qualifier (the connection name)
*
@@ -261,13 +309,31 @@ class GormEnhancer implements Closeable {
* @throws IllegalStateException if no instance API is found for the type
*/
static <D> GormInstanceApi<D> findInstanceApi(Class<D> entity, String qualifier = findTenantId(entity)) {
- def instanceApi = INSTANCE_APIS.get(qualifier)?.get(NameUtils.getClassName(entity))
+ String className = NameUtils.getClassName(entity)
+ def instanceApi = INSTANCE_APIS.get(qualifier)?.get(className)
+ if (instanceApi == null) {
+ instanceApi = initializeInstanceApi(entity, qualifier, className)
+ }
if (instanceApi == null) {
throw stateException(entity)
}
return instanceApi
}
+ private static <D> GormInstanceApi<D> initializeInstanceApi(Class<D> entity, String qualifier, String className) {
+ GormEnhancer enhancer = findEnhancer(entity, qualifier, className)
+ if (enhancer == null) {
+ return null
+ }
+ GormInstanceApi<D> instanceApi = INSTANCE_APIS.get(qualifier)?.get(className)
+ if (instanceApi != null) {
+ return instanceApi
+ }
+ instanceApi = enhancer.getInstanceApi(entity, qualifier)
+ INSTANCE_APIS.get(qualifier).put(className, instanceApi)
+ return instanceApi
+ }
+
/**
* Find a validation API for the give entity type and qualifier (the connection name)
*
@@ -278,11 +344,34 @@ class GormEnhancer implements Closeable {
* @throws IllegalStateException if no validation API is found for the type
*/
static <D> GormValidationApi<D> findValidationApi(Class<D> entity, String qualifier = findTenantId(entity)) {
- def instanceApi = VALIDATION_APIS.get(qualifier)?.get(NameUtils.getClassName(entity))
- if (instanceApi == null) {
+ String className = NameUtils.getClassName(entity)
+ def validationApi = VALIDATION_APIS.get(qualifier)?.get(className)
+ if (validationApi == null) {
+ validationApi = initializeValidationApi(entity, qualifier, className)
+ }
+ if (validationApi == null) {
throw stateException(entity)
}
- return instanceApi
+ return validationApi
+ }
+
+ private static <D> GormValidationApi<D> initializeValidationApi(Class<D> entity, String qualifier, String className) {
+ GormEnhancer enhancer = findEnhancer(entity, qualifier, className)
+ if (enhancer == null) {
+ return null
+ }
+ GormValidationApi<D> validationApi = VALIDATION_APIS.get(qualifier)?.get(className)
+ if (validationApi != null) {
+ return validationApi
+ }
+ validationApi = enhancer.getValidationApi(entity, qualifier)
+ VALIDATION_APIS.get(qualifier).put(className, validationApi)
+ return validationApi
+ }
+
+ private static GormEnhancer findEnhancer(Class entity, String qualifier, String className) {
+ Datastore datastore = DATASTORES.get(qualifier)?.get(className)
+ datastore != null ? ENHANCERS.get(datastore) : null
}
/**
@@ -384,6 +473,7 @@ class GormEnhancer implements Closeable {
@CompileStatic
void close() throws IOException {
removeConstraints()
+ ENHANCERS.remove(datastore)
DATASTORES_BY_TYPE.clear()
def registry = GroovySystem.metaClassRegistry
for (entity in datastore.mappingContext.persistentEntities) {
diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerAllQualifiersSpec.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerAllQualifiersSpec.groovy
index d7da91d7bc..5e87f5109d 100644
--- a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerAllQualifiersSpec.groovy
+++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerAllQualifiersSpec.groovy
@@ -24,11 +24,15 @@ import grails.gorm.MultiTenant
import org.grails.datastore.mapping.config.Entity
import org.grails.datastore.mapping.core.Datastore
import org.grails.datastore.mapping.core.connections.ConnectionSource
+import org.grails.datastore.mapping.core.connections.ConnectionSourceSettings
import org.grails.datastore.mapping.core.connections.ConnectionSources
import org.grails.datastore.mapping.core.connections.ConnectionSourcesProvider
import org.grails.datastore.mapping.model.ClassMapping
import org.grails.datastore.mapping.model.MappingContext
import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.datastore.mapping.multitenancy.MultiTenancySettings
+import org.grails.datastore.mapping.multitenancy.MultiTenantCapableDatastore
+import org.grails.datastore.mapping.multitenancy.TenantResolver
/**
* Tests for {@link GormEnhancer#allQualifiers(Datastore, PersistentEntity)} to verify
@@ -41,6 +45,12 @@ import org.grails.datastore.mapping.model.PersistentEntity
*/
class GormEnhancerAllQualifiersSpec extends Specification {
+ private Map<String, PersistentEntity> mockedEntities = [:]
+
+ void cleanup() {
+ mockedEntities.clear()
+ }
+
/**
* Create a GormEnhancer with a minimal mock datastore (no entities registered).
*/
@@ -64,28 +74,42 @@ class GormEnhancerAllQualifiersSpec extends Specification {
def classMapping = Mock(ClassMapping) {
getMappedForm() >> mappedForm
}
- Mock(PersistentEntity) {
+ def persistentEntity = Mock(PersistentEntity) {
getJavaClass() >> javaClass
getMapping() >> classMapping
getName() >> javaClass.name
}
+ mockedEntities[javaClass.name] = persistentEntity
+ persistentEntity
}
/**
* Create a mock datastore that also implements ConnectionSourcesProvider,
* returning the specified connection source names.
*/
- private Datastore mockMultiConnectionDatastore(List<String> connectionNames) {
+ private Datastore mockMultiConnectionDatastore(List<String> connectionNames, MultiTenancySettings.MultiTenancyMode mode = MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR) {
+ def connectionSourceSettings = new ConnectionSourceSettings()
+ connectionSourceSettings.multiTenancy.mode = mode
def connectionSourceMocks = connectionNames.collect { name ->
Mock(ConnectionSource) {
getName() >> name
+ getSettings() >> connectionSourceSettings
}
}
+ def defaultConnectionSource = connectionSourceMocks.find { it.name == ConnectionSource.DEFAULT } ?: connectionSourceMocks.first()
+ def mappingContext = Mock(MappingContext) {
+ getPersistentEntities() >> { mockedEntities.values() }
+ getPersistentEntity(_) >> { String name -> mockedEntities[name] }
+ }
def allSources = Mock(ConnectionSources) {
getAllConnectionSources() >> connectionSourceMocks
+ getDefaultConnectionSource() >> defaultConnectionSource
}
- Mock(TestConnectionSourcesProviderDatastore) {
+ Mock(TestMultiTenantConnectionSourcesProviderDatastore) {
getConnectionSources() >> allSources
+ getMappingContext() >> mappingContext
+ getMultiTenancyMode() >> mode
+ getTenantResolver() >> Mock(TenantResolver)
}
}
@@ -140,6 +164,74 @@ class GormEnhancerAllQualifiersSpec extends Specification {
qualifiers.size() == 3
}
+ void "registerEntity creates expanded MultiTenant qualifier APIs lazily"() {
+ given: "a MultiTenant entity that expands across many qualifiers"
+ def datastore = mockMultiConnectionDatastore([ConnectionSource.DEFAULT, 'tenantA', 'tenantB'])
+ def enhancer = new CountingGormEnhancer(datastore)
+ def entity = mockEntity(MultiTenantExpandedEntity, [ConnectionSource.DEFAULT])
+
+ when: "registering the entity"
+ enhancer.registerEntity(entity)
+
+ then: "only the default APIs are created eagerly"
+ enhancer.staticApiCount == 1
+ enhancer.instanceApiCount == 1
+ enhancer.validationApiCount == 1
+ GormEnhancer.@DATASTORES.get('tenantA').containsKey(entity.name)
+ !GormEnhancer.@STATIC_APIS.get('tenantA').containsKey(entity.name)
+ !GormEnhancer.@INSTANCE_APIS.get('tenantA').containsKey(entity.name)
+ !GormEnhancer.@VALIDATION_APIS.get('tenantA').containsKey(entity.name)
+
+ when: "the qualifier-specific APIs are requested"
+ def staticApi = GormEnhancer.findStaticApi(MultiTenantExpandedEntity, 'tenantA')
+ def instanceApi = GormEnhancer.findInstanceApi(MultiTenantExpandedEntity, 'tenantA')
+ def validationApi = GormEnhancer.findValidationApi(MultiTenantExpandedEntity, 'tenantA')
+
+ then: "registered qualifiers create APIs lazily without collapsing to the default datastore"
+ staticApi.is(GormEnhancer.findStaticApi(MultiTenantExpandedEntity, 'tenantA'))
+ instanceApi.is(GormEnhancer.findInstanceApi(MultiTenantExpandedEntity, 'tenantA'))
+ validationApi.is(GormEnhancer.findValidationApi(MultiTenantExpandedEntity, 'tenantA'))
+ !staticApi.is(GormEnhancer.findStaticApi(MultiTenantExpandedEntity, ConnectionSource.DEFAULT))
+ !instanceApi.is(GormEnhancer.findInstanceApi(MultiTenantExpandedEntity, ConnectionSource.DEFAULT))
+ !validationApi.is(GormEnhancer.findValidationApi(MultiTenantExpandedEntity, ConnectionSource.DEFAULT))
+ enhancer.staticApiCount == 2
+ enhancer.instanceApiCount == 2
+ enhancer.validationApiCount == 2
+ }
+
+ void "registerEntity creates tenant APIs lazily for database-per-tenant qualifiers"() {
+ given: "a database-per-tenant entity that expands across tenant qualifiers"
+ def datastore = mockMultiConnectionDatastore([ConnectionSource.DEFAULT, 'tenantA', 'tenantB'], MultiTenancySettings.MultiTenancyMode.DATABASE)
+ def enhancer = new CountingGormEnhancer(datastore)
+ def entity = mockEntity(DatabaseMultiTenantExpandedEntity, [ConnectionSource.DEFAULT])
+
+ when: "registering the entity"
+ enhancer.registerEntity(entity)
+
+ then: "only the default APIs are created eagerly"
+ enhancer.staticApiCount == 1
+ enhancer.instanceApiCount == 1
+ enhancer.validationApiCount == 1
+ GormEnhancer.@DATASTORES.get('tenantA').containsKey(entity.name)
+ !GormEnhancer.@STATIC_APIS.get('tenantA').containsKey(entity.name)
+ !GormEnhancer.@INSTANCE_APIS.get('tenantA').containsKey(entity.name)
+ !GormEnhancer.@VALIDATION_APIS.get('tenantA').containsKey(entity.name)
+
+ when: "the tenant-specific APIs are requested"
+ def staticApi = GormEnhancer.findStaticApi(DatabaseMultiTenantExpandedEntity, 'tenantA')
+ def instanceApi = GormEnhancer.findInstanceApi(DatabaseMultiTenantExpandedEntity, 'tenantA')
+ def validationApi = GormEnhancer.findValidationApi(DatabaseMultiTenantExpandedEntity, 'tenantA')
+
+ then: "database-per-tenant APIs are created lazily and cached per tenant datastore"
+ staticApi.is(GormEnhancer.findStaticApi(DatabaseMultiTenantExpandedEntity, 'tenantA'))
+ instanceApi.is(GormEnhancer.findInstanceApi(DatabaseMultiTenantExpandedEntity, 'tenantA'))
+ validationApi.is(GormEnhancer.findValidationApi(DatabaseMultiTenantExpandedEntity, 'tenantA'))
+ !staticApi.is(GormEnhancer.findStaticApi(DatabaseMultiTenantExpandedEntity, ConnectionSource.DEFAULT))
+ enhancer.staticApiCount == 2
+ enhancer.instanceApiCount == 2
+ enhancer.validationApiCount == 2
+ }
+
void "MultiTenant entity with ALL datasource expands to all qualifiers"() {
given: "a MultiTenant entity declared with ConnectionSource.ALL"
def enhancer = createEnhancer()
@@ -225,12 +317,43 @@ class GormEnhancerAllQualifiersSpec extends Specification {
static class MultiTenantDefaultEntity implements MultiTenant<MultiTenantDefaultEntity> {}
static class MultiTenantAllEntity implements MultiTenant<MultiTenantAllEntity> {}
static class MultiTenantMultiDsEntity implements MultiTenant<MultiTenantMultiDsEntity> {}
+ static class MultiTenantExpandedEntity implements MultiTenant<MultiTenantExpandedEntity> {}
+ static class DatabaseMultiTenantExpandedEntity implements MultiTenant<DatabaseMultiTenantExpandedEntity> {}
static class NonMultiTenantSecondaryEntity {}
static class NonMultiTenantDefaultEntity {}
static class NonMultiTenantAllEntity {}
+ static class CountingGormEnhancer extends GormEnhancer {
+
+ int staticApiCount
+ int instanceApiCount
+ int validationApiCount
+
+ CountingGormEnhancer(Datastore datastore) {
+ super(datastore)
+ }
+
+ @Override
+ protected <D> GormStaticApi<D> getStaticApi(Class<D> cls, String qualifier) {
+ staticApiCount++
+ super.getStaticApi(cls, qualifier)
+ }
+
+ @Override
+ protected <D> GormInstanceApi<D> getInstanceApi(Class<D> cls, String qualifier) {
+ instanceApiCount++
+ super.getInstanceApi(cls, qualifier)
+ }
+
+ @Override
+ protected <D> GormValidationApi<D> getValidationApi(Class<D> cls, String qualifier) {
+ validationApiCount++
+ super.getValidationApi(cls, qualifier)
+ }
+ }
+
/**
* Combined interface so Spock can mock a Datastore that also provides ConnectionSources.
*/
- static interface TestConnectionSourcesProviderDatastore extends Datastore, ConnectionSourcesProvider {}
+ static interface TestMultiTenantConnectionSourcesProviderDatastore extends MultiTenantCapableDatastore<Object, ConnectionSourceSettings> {}
}
(grails-core) 02/02: Cover Hibernate GORM API allocation combinations
Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch fix/gorm-api-registration-scaling
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 2498f91f6061fd6c27c79814bd627df3d6ee196f
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Fri Jun 26 15:42:33 2026 -0400
Cover Hibernate GORM API allocation combinations
Add mirrored Hibernate 5 and Hibernate 7 regression specs for the
GORM API allocation matrix across discriminator, schema, and database
multi-tenancy modes, explicit datasources, multiple datasources, and
ConnectionSource.ALL mappings.
Assisted-by: Hephaestus:openai/gpt-5.5
---
.../connections/GormApiAllocationSpec.groovy | 282 +++++++++++++++++++++
.../connections/GormApiAllocationSpec.groovy | 282 +++++++++++++++++++++
2 files changed, 564 insertions(+)
diff --git a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/GormApiAllocationSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/GormApiAllocationSpec.groovy
new file mode 100644
index 0000000000..10b88ad910
--- /dev/null
+++ b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/GormApiAllocationSpec.groovy
@@ -0,0 +1,282 @@
+/*
+ * 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.connections
+
+import java.util.UUID
+
+import org.hibernate.dialect.H2Dialect
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+import spock.util.environment.RestoreSystemProperties
+
+import grails.gorm.MultiTenant
+import grails.gorm.annotation.Entity
+import org.grails.datastore.gorm.GormEnhancer
+import org.grails.datastore.gorm.GormEntity
+import org.grails.datastore.mapping.core.DatastoreUtils
+import org.grails.datastore.mapping.core.connections.ConnectionSource
+import org.grails.datastore.mapping.multitenancy.AllTenantsResolver
+import org.grails.datastore.mapping.multitenancy.MultiTenancySettings
+import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver
+import org.grails.orm.hibernate.HibernateDatastore
+
+@RestoreSystemProperties
+class GormApiAllocationSpec extends Specification {
+
+ @AutoCleanup HibernateDatastore datastore
+
+ void cleanup() {
+ System.clearProperty(SystemPropertyTenantResolver.PROPERTY_NAME)
+ }
+
+ void "DISCRIMINATOR multi tenant single datasource uses only default APIs"() {
+ when:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, [], H5AllocationDefaultTenant)
+
+ then:
+ hasAllApis(H5AllocationDefaultTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H5AllocationDefaultTenant, 'tenantA')
+
+ when:
+ def result = H5AllocationDefaultTenant.withTenant('tenantA') { true }
+
+ then:
+ result
+ !hasAnyApis(H5AllocationDefaultTenant, 'tenantA')
+ }
+
+ void "DISCRIMINATOR multi tenant default datasource with multiple configured datasources creates non-default APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting'], H5AllocationDefaultTenant)
+
+ expect:
+ hasAllApis(H5AllocationDefaultTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H5AllocationDefaultTenant, 'analytics')
+ !hasAnyApis(H5AllocationDefaultTenant, 'reporting')
+
+ when:
+ def staticApi = GormEnhancer.findStaticApi(H5AllocationDefaultTenant, 'analytics')
+ def instanceApi = GormEnhancer.findInstanceApi(H5AllocationDefaultTenant, 'analytics')
+ def validationApi = GormEnhancer.findValidationApi(H5AllocationDefaultTenant, 'analytics')
+
+ then:
+ hasAllApis(H5AllocationDefaultTenant, 'analytics')
+ !hasAnyApis(H5AllocationDefaultTenant, 'reporting')
+ !staticApi.is(GormEnhancer.findStaticApi(H5AllocationDefaultTenant, ConnectionSource.DEFAULT))
+ instanceApi.is(GormEnhancer.findInstanceApi(H5AllocationDefaultTenant, 'analytics'))
+ validationApi.is(GormEnhancer.findValidationApi(H5AllocationDefaultTenant, 'analytics'))
+ }
+
+ void "SCHEMA multi tenant single datasource creates tenant APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.SCHEMA, [], H5AllocationSchemaTenant)
+
+ expect:
+ hasAllApis(H5AllocationSchemaTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H5AllocationSchemaTenant, 'tenantA')
+ !hasAnyApis(H5AllocationSchemaTenant, 'tenantB')
+
+ when:
+ def staticApi = GormEnhancer.findStaticApi(H5AllocationSchemaTenant, 'tenantA')
+ def instanceApi = GormEnhancer.findInstanceApi(H5AllocationSchemaTenant, 'tenantA')
+ def validationApi = GormEnhancer.findValidationApi(H5AllocationSchemaTenant, 'tenantA')
+
+ then:
+ hasAllApis(H5AllocationSchemaTenant, 'tenantA')
+ !hasAnyApis(H5AllocationSchemaTenant, 'tenantB')
+ !staticApi.is(GormEnhancer.findStaticApi(H5AllocationSchemaTenant, ConnectionSource.DEFAULT))
+ instanceApi.is(GormEnhancer.findInstanceApi(H5AllocationSchemaTenant, 'tenantA'))
+ validationApi.is(GormEnhancer.findValidationApi(H5AllocationSchemaTenant, 'tenantA'))
+ }
+
+ void "DATABASE multi tenant creates tenant APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DATABASE, ['tenantA', 'tenantB'], H5AllocationDatabaseTenant)
+
+ expect:
+ hasAllApis(H5AllocationDatabaseTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H5AllocationDatabaseTenant, 'tenantA')
+ !hasAnyApis(H5AllocationDatabaseTenant, 'tenantB')
+
+ when:
+ def staticApi = GormEnhancer.findStaticApi(H5AllocationDatabaseTenant, 'tenantA')
+ def instanceApi = GormEnhancer.findInstanceApi(H5AllocationDatabaseTenant, 'tenantA')
+ def validationApi = GormEnhancer.findValidationApi(H5AllocationDatabaseTenant, 'tenantA')
+
+ then:
+ hasAllApis(H5AllocationDatabaseTenant, 'tenantA')
+ !hasAnyApis(H5AllocationDatabaseTenant, 'tenantB')
+ !staticApi.is(GormEnhancer.findStaticApi(H5AllocationDatabaseTenant, ConnectionSource.DEFAULT))
+ instanceApi.is(GormEnhancer.findInstanceApi(H5AllocationDatabaseTenant, 'tenantA'))
+ validationApi.is(GormEnhancer.findValidationApi(H5AllocationDatabaseTenant, 'tenantA'))
+ }
+
+ void "DISCRIMINATOR multi tenant explicit datasource allocates only mapped datasource APIs"() {
+ when:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting'], H5AllocationAnalyticsTenant)
+
+ then:
+ hasAllApis(H5AllocationAnalyticsTenant, ConnectionSource.DEFAULT)
+ hasAllApis(H5AllocationAnalyticsTenant, 'analytics')
+ !hasAnyApis(H5AllocationAnalyticsTenant, 'reporting')
+ }
+
+ void "DISCRIMINATOR multi tenant explicit multiple datasources allocates only mapped datasource APIs"() {
+ when:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting', 'audit'], H5AllocationMultipleDatasourceTenant)
+
+ then:
+ hasAllApis(H5AllocationMultipleDatasourceTenant, ConnectionSource.DEFAULT)
+ hasAllApis(H5AllocationMultipleDatasourceTenant, 'analytics')
+ hasAllApis(H5AllocationMultipleDatasourceTenant, 'reporting')
+ !hasAnyApis(H5AllocationMultipleDatasourceTenant, 'audit')
+ }
+
+ void "DISCRIMINATOR multi tenant ALL datasource creates non-default datasource APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting'], H5AllocationAllDatasourceTenant)
+
+ expect:
+ hasAllApis(H5AllocationAllDatasourceTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H5AllocationAllDatasourceTenant, 'analytics')
+ !hasAnyApis(H5AllocationAllDatasourceTenant, 'reporting')
+
+ when:
+ GormEnhancer.findStaticApi(H5AllocationAllDatasourceTenant, 'analytics')
+ GormEnhancer.findInstanceApi(H5AllocationAllDatasourceTenant, 'analytics')
+ GormEnhancer.findValidationApi(H5AllocationAllDatasourceTenant, 'analytics')
+
+ then:
+ hasAllApis(H5AllocationAllDatasourceTenant, 'analytics')
+ !hasAnyApis(H5AllocationAllDatasourceTenant, 'reporting')
+ }
+
+ private HibernateDatastore newDatastore(MultiTenancySettings.MultiTenancyMode mode, List<String> extraDataSources, Class<?>... domainClasses) {
+ Map config = baseConfig(mode, domainClasses[0].simpleName)
+ extraDataSources.each { String name ->
+ config["dataSources.${name}.url".toString()] = "jdbc:h2:mem:${databaseName(domainClasses[0].simpleName + name)};LOCK_TIMEOUT=10000".toString()
+ }
+ new HibernateDatastore(DatastoreUtils.createPropertyResolver(config), domainClasses)
+ }
+
+ private static Map baseConfig(MultiTenancySettings.MultiTenancyMode mode, String name) {
+ [
+ 'grails.gorm.multiTenancy.mode': mode,
+ 'grails.gorm.multiTenancy.tenantResolverClass': H5AllocationTenantResolver,
+ 'dataSource.url': "jdbc:h2:mem:${databaseName(name)};LOCK_TIMEOUT=10000".toString(),
+ 'dataSource.dbCreate': mode == MultiTenancySettings.MultiTenancyMode.SCHEMA ? 'update' : 'create-drop',
+ 'dataSource.dialect': H2Dialect.name,
+ 'dataSource.formatSql': 'true',
+ 'hibernate.flush.mode': 'COMMIT',
+ 'hibernate.hbm2ddl.auto': mode == MultiTenancySettings.MultiTenancyMode.SCHEMA ? 'create' : 'create-drop'
+ ]
+ }
+
+ private static String databaseName(String name) {
+ "h5_${name}_${UUID.randomUUID().toString().replace('-', '')}"
+ }
+
+ private static boolean hasAllApis(Class<?> type, String qualifier) {
+ hasStaticApi(type, qualifier) && hasInstanceApi(type, qualifier) && hasValidationApi(type, qualifier)
+ }
+
+ private static boolean hasAnyApis(Class<?> type, String qualifier) {
+ hasStaticApi(type, qualifier) || hasInstanceApi(type, qualifier) || hasValidationApi(type, qualifier)
+ }
+
+ private static boolean hasStaticApi(Class<?> type, String qualifier) {
+ GormEnhancer.@STATIC_APIS.get(qualifier).containsKey(type.name)
+ }
+
+ private static boolean hasInstanceApi(Class<?> type, String qualifier) {
+ GormEnhancer.@INSTANCE_APIS.get(qualifier).containsKey(type.name)
+ }
+
+ private static boolean hasValidationApi(Class<?> type, String qualifier) {
+ GormEnhancer.@VALIDATION_APIS.get(qualifier).containsKey(type.name)
+ }
+}
+
+class H5AllocationTenantResolver extends SystemPropertyTenantResolver implements AllTenantsResolver {
+
+ @Override
+ Iterable<Serializable> resolveTenantIds() {
+ ['tenantA', 'tenantB']
+ }
+}
+
+@Entity
+class H5AllocationDefaultTenant implements GormEntity<H5AllocationDefaultTenant>, MultiTenant<H5AllocationDefaultTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+}
+
+@Entity
+class H5AllocationSchemaTenant implements GormEntity<H5AllocationSchemaTenant>, MultiTenant<H5AllocationSchemaTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+}
+
+@Entity
+class H5AllocationDatabaseTenant implements GormEntity<H5AllocationDatabaseTenant>, MultiTenant<H5AllocationDatabaseTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+}
+
+@Entity
+class H5AllocationAnalyticsTenant implements GormEntity<H5AllocationAnalyticsTenant>, MultiTenant<H5AllocationAnalyticsTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+
+ static mapping = {
+ datasource 'analytics'
+ }
+}
+
+@Entity
+class H5AllocationMultipleDatasourceTenant implements GormEntity<H5AllocationMultipleDatasourceTenant>, MultiTenant<H5AllocationMultipleDatasourceTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+
+ static mapping = {
+ datasources(['analytics', 'reporting'])
+ }
+}
+
+@Entity
+class H5AllocationAllDatasourceTenant implements GormEntity<H5AllocationAllDatasourceTenant>, MultiTenant<H5AllocationAllDatasourceTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+
+ static mapping = {
+ datasource ConnectionSource.ALL
+ }
+}
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/connections/GormApiAllocationSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/connections/GormApiAllocationSpec.groovy
new file mode 100644
index 0000000000..9b53ca9322
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/connections/GormApiAllocationSpec.groovy
@@ -0,0 +1,282 @@
+/*
+ * 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.connections
+
+import java.util.UUID
+
+import org.hibernate.dialect.H2Dialect
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+import spock.util.environment.RestoreSystemProperties
+
+import grails.gorm.MultiTenant
+import grails.gorm.annotation.Entity
+import org.grails.datastore.gorm.GormEnhancer
+import org.grails.datastore.gorm.GormEntity
+import org.grails.datastore.mapping.core.DatastoreUtils
+import org.grails.datastore.mapping.core.connections.ConnectionSource
+import org.grails.datastore.mapping.multitenancy.AllTenantsResolver
+import org.grails.datastore.mapping.multitenancy.MultiTenancySettings
+import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver
+import org.grails.orm.hibernate.HibernateDatastore
+
+@RestoreSystemProperties
+class GormApiAllocationSpec extends Specification {
+
+ @AutoCleanup HibernateDatastore datastore
+
+ void cleanup() {
+ System.clearProperty(SystemPropertyTenantResolver.PROPERTY_NAME)
+ }
+
+ void "DISCRIMINATOR multi tenant single datasource uses only default APIs"() {
+ when:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, [], H7AllocationDefaultTenant)
+
+ then:
+ hasAllApis(H7AllocationDefaultTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H7AllocationDefaultTenant, 'tenantA')
+
+ when:
+ def result = H7AllocationDefaultTenant.withTenant('tenantA') { true }
+
+ then:
+ result
+ !hasAnyApis(H7AllocationDefaultTenant, 'tenantA')
+ }
+
+ void "DISCRIMINATOR multi tenant default datasource with multiple configured datasources creates non-default APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting'], H7AllocationDefaultTenant)
+
+ expect:
+ hasAllApis(H7AllocationDefaultTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H7AllocationDefaultTenant, 'analytics')
+ !hasAnyApis(H7AllocationDefaultTenant, 'reporting')
+
+ when:
+ def staticApi = GormEnhancer.findStaticApi(H7AllocationDefaultTenant, 'analytics')
+ def instanceApi = GormEnhancer.findInstanceApi(H7AllocationDefaultTenant, 'analytics')
+ def validationApi = GormEnhancer.findValidationApi(H7AllocationDefaultTenant, 'analytics')
+
+ then:
+ hasAllApis(H7AllocationDefaultTenant, 'analytics')
+ !hasAnyApis(H7AllocationDefaultTenant, 'reporting')
+ !staticApi.is(GormEnhancer.findStaticApi(H7AllocationDefaultTenant, ConnectionSource.DEFAULT))
+ instanceApi.is(GormEnhancer.findInstanceApi(H7AllocationDefaultTenant, 'analytics'))
+ validationApi.is(GormEnhancer.findValidationApi(H7AllocationDefaultTenant, 'analytics'))
+ }
+
+ void "SCHEMA multi tenant single datasource creates tenant APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.SCHEMA, [], H7AllocationSchemaTenant)
+
+ expect:
+ hasAllApis(H7AllocationSchemaTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H7AllocationSchemaTenant, 'tenantA')
+ !hasAnyApis(H7AllocationSchemaTenant, 'tenantB')
+
+ when:
+ def staticApi = GormEnhancer.findStaticApi(H7AllocationSchemaTenant, 'tenantA')
+ def instanceApi = GormEnhancer.findInstanceApi(H7AllocationSchemaTenant, 'tenantA')
+ def validationApi = GormEnhancer.findValidationApi(H7AllocationSchemaTenant, 'tenantA')
+
+ then:
+ hasAllApis(H7AllocationSchemaTenant, 'tenantA')
+ !hasAnyApis(H7AllocationSchemaTenant, 'tenantB')
+ !staticApi.is(GormEnhancer.findStaticApi(H7AllocationSchemaTenant, ConnectionSource.DEFAULT))
+ instanceApi.is(GormEnhancer.findInstanceApi(H7AllocationSchemaTenant, 'tenantA'))
+ validationApi.is(GormEnhancer.findValidationApi(H7AllocationSchemaTenant, 'tenantA'))
+ }
+
+ void "DATABASE multi tenant creates tenant APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DATABASE, ['tenantA', 'tenantB'], H7AllocationDatabaseTenant)
+
+ expect:
+ hasAllApis(H7AllocationDatabaseTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H7AllocationDatabaseTenant, 'tenantA')
+ !hasAnyApis(H7AllocationDatabaseTenant, 'tenantB')
+
+ when:
+ def staticApi = GormEnhancer.findStaticApi(H7AllocationDatabaseTenant, 'tenantA')
+ def instanceApi = GormEnhancer.findInstanceApi(H7AllocationDatabaseTenant, 'tenantA')
+ def validationApi = GormEnhancer.findValidationApi(H7AllocationDatabaseTenant, 'tenantA')
+
+ then:
+ hasAllApis(H7AllocationDatabaseTenant, 'tenantA')
+ !hasAnyApis(H7AllocationDatabaseTenant, 'tenantB')
+ !staticApi.is(GormEnhancer.findStaticApi(H7AllocationDatabaseTenant, ConnectionSource.DEFAULT))
+ instanceApi.is(GormEnhancer.findInstanceApi(H7AllocationDatabaseTenant, 'tenantA'))
+ validationApi.is(GormEnhancer.findValidationApi(H7AllocationDatabaseTenant, 'tenantA'))
+ }
+
+ void "DISCRIMINATOR multi tenant explicit datasource allocates only mapped datasource APIs"() {
+ when:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting'], H7AllocationAnalyticsTenant)
+
+ then:
+ hasAllApis(H7AllocationAnalyticsTenant, ConnectionSource.DEFAULT)
+ hasAllApis(H7AllocationAnalyticsTenant, 'analytics')
+ !hasAnyApis(H7AllocationAnalyticsTenant, 'reporting')
+ }
+
+ void "DISCRIMINATOR multi tenant explicit multiple datasources allocates only mapped datasource APIs"() {
+ when:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting', 'audit'], H7AllocationMultipleDatasourceTenant)
+
+ then:
+ hasAllApis(H7AllocationMultipleDatasourceTenant, ConnectionSource.DEFAULT)
+ hasAllApis(H7AllocationMultipleDatasourceTenant, 'analytics')
+ hasAllApis(H7AllocationMultipleDatasourceTenant, 'reporting')
+ !hasAnyApis(H7AllocationMultipleDatasourceTenant, 'audit')
+ }
+
+ void "DISCRIMINATOR multi tenant ALL datasource creates non-default datasource APIs lazily"() {
+ given:
+ datastore = newDatastore(MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, ['analytics', 'reporting'], H7AllocationAllDatasourceTenant)
+
+ expect:
+ hasAllApis(H7AllocationAllDatasourceTenant, ConnectionSource.DEFAULT)
+ !hasAnyApis(H7AllocationAllDatasourceTenant, 'analytics')
+ !hasAnyApis(H7AllocationAllDatasourceTenant, 'reporting')
+
+ when:
+ GormEnhancer.findStaticApi(H7AllocationAllDatasourceTenant, 'analytics')
+ GormEnhancer.findInstanceApi(H7AllocationAllDatasourceTenant, 'analytics')
+ GormEnhancer.findValidationApi(H7AllocationAllDatasourceTenant, 'analytics')
+
+ then:
+ hasAllApis(H7AllocationAllDatasourceTenant, 'analytics')
+ !hasAnyApis(H7AllocationAllDatasourceTenant, 'reporting')
+ }
+
+ private HibernateDatastore newDatastore(MultiTenancySettings.MultiTenancyMode mode, List<String> extraDataSources, Class<?>... domainClasses) {
+ Map config = baseConfig(mode, domainClasses[0].simpleName)
+ extraDataSources.each { String name ->
+ config["dataSources.${name}.url".toString()] = "jdbc:h2:mem:${databaseName(domainClasses[0].simpleName + name)};LOCK_TIMEOUT=10000".toString()
+ }
+ new HibernateDatastore(DatastoreUtils.createPropertyResolver(config), domainClasses)
+ }
+
+ private static Map baseConfig(MultiTenancySettings.MultiTenancyMode mode, String name) {
+ [
+ 'grails.gorm.multiTenancy.mode': mode,
+ 'grails.gorm.multiTenancy.tenantResolverClass': H7AllocationTenantResolver,
+ 'dataSource.url': "jdbc:h2:mem:${databaseName(name)};LOCK_TIMEOUT=10000".toString(),
+ 'dataSource.dbCreate': mode == MultiTenancySettings.MultiTenancyMode.SCHEMA ? 'update' : 'create-drop',
+ 'dataSource.dialect': H2Dialect.name,
+ 'dataSource.formatSql': 'true',
+ 'hibernate.flush.mode': 'COMMIT',
+ 'hibernate.hbm2ddl.auto': mode == MultiTenancySettings.MultiTenancyMode.SCHEMA ? 'create' : 'create-drop'
+ ]
+ }
+
+ private static String databaseName(String name) {
+ "h7_${name}_${UUID.randomUUID().toString().replace('-', '')}"
+ }
+
+ private static boolean hasAllApis(Class<?> type, String qualifier) {
+ hasStaticApi(type, qualifier) && hasInstanceApi(type, qualifier) && hasValidationApi(type, qualifier)
+ }
+
+ private static boolean hasAnyApis(Class<?> type, String qualifier) {
+ hasStaticApi(type, qualifier) || hasInstanceApi(type, qualifier) || hasValidationApi(type, qualifier)
+ }
+
+ private static boolean hasStaticApi(Class<?> type, String qualifier) {
+ GormEnhancer.@STATIC_APIS.get(qualifier).containsKey(type.name)
+ }
+
+ private static boolean hasInstanceApi(Class<?> type, String qualifier) {
+ GormEnhancer.@INSTANCE_APIS.get(qualifier).containsKey(type.name)
+ }
+
+ private static boolean hasValidationApi(Class<?> type, String qualifier) {
+ GormEnhancer.@VALIDATION_APIS.get(qualifier).containsKey(type.name)
+ }
+}
+
+class H7AllocationTenantResolver extends SystemPropertyTenantResolver implements AllTenantsResolver {
+
+ @Override
+ Iterable<Serializable> resolveTenantIds() {
+ ['tenantA', 'tenantB']
+ }
+}
+
+@Entity
+class H7AllocationDefaultTenant implements GormEntity<H7AllocationDefaultTenant>, MultiTenant<H7AllocationDefaultTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+}
+
+@Entity
+class H7AllocationSchemaTenant implements GormEntity<H7AllocationSchemaTenant>, MultiTenant<H7AllocationSchemaTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+}
+
+@Entity
+class H7AllocationDatabaseTenant implements GormEntity<H7AllocationDatabaseTenant>, MultiTenant<H7AllocationDatabaseTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+}
+
+@Entity
+class H7AllocationAnalyticsTenant implements GormEntity<H7AllocationAnalyticsTenant>, MultiTenant<H7AllocationAnalyticsTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+
+ static mapping = {
+ datasource 'analytics'
+ }
+}
+
+@Entity
+class H7AllocationMultipleDatasourceTenant implements GormEntity<H7AllocationMultipleDatasourceTenant>, MultiTenant<H7AllocationMultipleDatasourceTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+
+ static mapping = {
+ datasources(['analytics', 'reporting'])
+ }
+}
+
+@Entity
+class H7AllocationAllDatasourceTenant implements GormEntity<H7AllocationAllDatasourceTenant>, MultiTenant<H7AllocationAllDatasourceTenant> {
+ Long id
+ Long version
+ String tenantId
+ String name
+
+ static mapping = {
+ datasource ConnectionSource.ALL
+ }
+}