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 c82b88776ba49c22a7db349b97386e8bef4f206d Author: Walter Duque de Estrada <[email protected]> AuthorDate: Thu Mar 19 07:46:40 2026 -0500 hibernate 7: ### Build issue **Issue** misconfiguration of grails-data-tck-config.gradle and core build.gradle ### Global Naming Strategy Pollution (Fixed) **Issue:** `GrailsDomainBinder` used a static `NamingStrategyProvider`, causing naming strategies configured in one test to persist into others. **Action:** Refactored `NamingStrategyProvider` into an instance-based field. Updated `GrailsDomainBinder`, `HibernateMappingContextConfiguration`, and `HibernateConnectionSourceFactory` to pass this instance through the configuration lifecycle. ### Static Mapping Cache (Fixed) **Issue:** `MappingCacheHolder` was a static singleton. While `HibernateDatastore` attempted to clear it, direct usage of binders in unit tests often bypassed this cleanup, leading to stale mappings persisting between tests. **Action:** Refactored `MappingCacheHolder` to remove the static singleton (`getInstance()`). The cache is now instance-based and maintained by `HibernateMappingContext`. All consumers (`GrailsDomainBinder`, `HibernateDatastore`, `HibernateHqlQuery`, etc.) were updated to use the instance tied to their specific configuration cycle, eliminating cross-test pollution. --- gradle/grails-data-tck-config.gradle | 8 ++-- grails-data-hibernate7/core/build.gradle | 5 +++ .../grails/orm/hibernate/HibernateDatastore.java | 2 +- .../orm/hibernate/cfg/HibernateMappingContext.java | 5 +++ .../cfg/HibernateMappingContextConfiguration.java | 21 +++++++++- .../orm/hibernate/cfg/MappingCacheHolder.java | 8 +--- .../domainbinding/binder/GrailsDomainBinder.java | 47 +++++++++++----------- .../HibernateConnectionSourceFactory.java | 10 +++-- .../orm/hibernate/query/HibernateHqlQuery.java | 3 +- .../orm/hibernate/query/HqlListQueryBuilder.java | 4 +- 10 files changed, 71 insertions(+), 42 deletions(-) diff --git a/gradle/grails-data-tck-config.gradle b/gradle/grails-data-tck-config.gradle index ea0b5ad1a9..ead5c5ef0e 100644 --- a/gradle/grails-data-tck-config.gradle +++ b/gradle/grails-data-tck-config.gradle @@ -97,8 +97,8 @@ tasks.withType(Test).configureEach { Test it -> } true - } + } - it.testClassesDirs = objects.fileCollection().from(extractTck, testClassesDirs) - it.finalizedBy(cleanupTask) -} + it.testClassesDirs = project.files(extractTck.map { task -> task.outputs.files }, sourceSets.test.output.classesDirs) + it.finalizedBy(cleanupTask) + } diff --git a/grails-data-hibernate7/core/build.gradle b/grails-data-hibernate7/core/build.gradle index 10e808337c..188fa62be9 100644 --- a/grails-data-hibernate7/core/build.gradle +++ b/grails-data-hibernate7/core/build.gradle @@ -126,6 +126,11 @@ dependencies { } +sourceSets { + test { + groovy.srcDirs = ['src/test/groovy'] + } +} apply { from rootProject.layout.projectDirectory.file('gradle/hibernate7-test-config.gradle') 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 40afaf588f..fc85f25ea0 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 @@ -751,7 +751,7 @@ public class HibernateDatastore extends AbstractDatastore LOG.error("There was an error shutting down GORM for an entity: {}", e.getMessage(), e); } } finally { - MappingCacheHolder.getInstance().clear(); + getMappingContext().getMappingCacheHolder().clear(); try { if (this.gormEnhancer != null) { this.gormEnhancer.close(); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java index a92e760f28..34d66e095b 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java @@ -41,6 +41,11 @@ public class HibernateMappingContext extends AbstractMappingContext { private final HibernateMappingFactory mappingFactory; private final MappingConfigurationStrategy syntaxStrategy; + private final MappingCacheHolder mappingCacheHolder = new MappingCacheHolder(); + + public MappingCacheHolder getMappingCacheHolder() { + return mappingCacheHolder; + } public HibernateMappingContext( HibernateConnectionSourceSettings settings, Object contextObject, Class... persistentClasses) { diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.java index ddeaac06fc..2689df86d7 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.java @@ -72,6 +72,7 @@ import org.grails.orm.hibernate.GrailsSessionContext; import org.grails.orm.hibernate.HibernateEventListeners; import org.grails.orm.hibernate.MetadataIntegrator; import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsDomainBinder; +import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyProvider; /** * A Configuration that uses a MappingContext to configure Hibernate @@ -102,6 +103,19 @@ public class HibernateMappingContextConfiguration extends Configuration private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); // private MetadataContributor metadataContributor; private final Set<Class> additionalClasses = new HashSet<>(); + private NamingStrategyProvider namingStrategyProvider = new NamingStrategyProvider(); + + public NamingStrategyProvider getNamingStrategyProvider() { + return namingStrategyProvider; + } + + public void setNamingStrategyProvider(NamingStrategyProvider namingStrategyProvider) { + this.namingStrategyProvider = namingStrategyProvider; + } + + public MappingCacheHolder getMappingCacheHolder() { + return hibernateMappingContext != null ? hibernateMappingContext.getMappingCacheHolder() : null; + } public void setHibernateMappingContext(HibernateMappingContext hibernateMappingContext) { this.hibernateMappingContext = hibernateMappingContext; @@ -261,7 +275,12 @@ public class HibernateMappingContextConfiguration extends Configuration ConfigurationHelper.resolvePlaceHolders(getProperties()); final GrailsDomainBinder domainBinder = - new GrailsDomainBinder(dataSourceName, sessionFactoryBeanName, hibernateMappingContext); + new GrailsDomainBinder( + dataSourceName, + sessionFactoryBeanName, + hibernateMappingContext, + namingStrategyProvider, + hibernateMappingContext.getMappingCacheHolder()); List<Class> annotatedClasses = new ArrayList<>(); for (PersistentEntity persistentEntity : hibernateMappingContext.getPersistentEntities()) { diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/MappingCacheHolder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/MappingCacheHolder.java index 8e3828f5b4..1083a5fdd9 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/MappingCacheHolder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/MappingCacheHolder.java @@ -27,13 +27,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersi /** Holder for the GORM mapping cache. */ public class MappingCacheHolder { - private static final MappingCacheHolder INSTANCE = new MappingCacheHolder(); - - private MappingCacheHolder() {} - - public static MappingCacheHolder getInstance() { - return INSTANCE; - } + public MappingCacheHolder() {} private final Map<Class<?>, Mapping> MAPPING_CACHE = new HashMap<>(); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsDomainBinder.java index c70e8f8417..61f40fd6b9 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsDomainBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsDomainBinder.java @@ -49,23 +49,6 @@ import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyWrapper; import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator; import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher; -import org.grails.orm.hibernate.cfg.HibernateMappingContext; -import org.grails.orm.hibernate.cfg.MappingCacheHolder; -import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy; -import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder; -import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernatePersistentEntity; -import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover; -import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator; -import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher; -import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher; -import org.grails.orm.hibernate.cfg.domainbinding.util.GrailsPropertyResolver; -import org.grails.orm.hibernate.cfg.domainbinding.util.MultiTenantFilterBinder; -import org.grails.orm.hibernate.cfg.domainbinding.util.MultiTenantFilterDefinitionBinder; -import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyProvider; -import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyWrapper; -import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator; -import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher; - /** * 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. @@ -82,14 +65,12 @@ public class GrailsDomainBinder implements AdditionalMappingContributor, TypeCon public static final String ENUM_CLASS_PROP = "enumClass"; public static final Logger LOG = LoggerFactory.getLogger(GrailsDomainBinder.class); - /** Provider for naming strategies */ - private static final NamingStrategyProvider NAMING_STRATEGY_PROVIDER = new NamingStrategyProvider(); - public static final String JPA_DEFAULT_DISCRIMINATOR_TYPE = "DTYPE"; private final String sessionFactoryName; private final String dataSourceName; private final HibernateMappingContext hibernateMappingContext; + private final NamingStrategyProvider namingStrategyProvider; private PersistentEntityNamingStrategy namingStrategy; private MetadataBuildingContext metadataBuildingContext; private final MappingCacheHolder mappingCacheHolder; @@ -100,10 +81,20 @@ public class GrailsDomainBinder implements AdditionalMappingContributor, TypeCon public GrailsDomainBinder( String dataSourceName, String sessionFactoryName, HibernateMappingContext hibernateMappingContext) { + this(dataSourceName, sessionFactoryName, hibernateMappingContext, new NamingStrategyProvider(), new MappingCacheHolder()); + } + + public GrailsDomainBinder( + String dataSourceName, + String sessionFactoryName, + HibernateMappingContext hibernateMappingContext, + NamingStrategyProvider namingStrategyProvider, + MappingCacheHolder mappingCacheHolder) { this.sessionFactoryName = sessionFactoryName; this.dataSourceName = dataSourceName; this.hibernateMappingContext = hibernateMappingContext; - this.mappingCacheHolder = MappingCacheHolder.getInstance(); + this.namingStrategyProvider = namingStrategyProvider; + this.mappingCacheHolder = mappingCacheHolder; // pre-build mappings for (HibernatePersistentEntity persistentEntity : @@ -254,15 +245,15 @@ public class GrailsDomainBinder implements AdditionalMappingContributor, TypeCon * @throws InstantiationException When an error occurred instantiating the strategy * @throws IllegalAccessException When an error occurred instantiating the strategy */ - public static void configureNamingStrategy(final String datasourceName, final Object strategy) + public void configureNamingStrategy(final String datasourceName, final Object strategy) throws ClassNotFoundException, InstantiationException, IllegalAccessException { - NAMING_STRATEGY_PROVIDER.configureNamingStrategy(datasourceName, strategy); + namingStrategyProvider.configureNamingStrategy(datasourceName, strategy); } public PersistentEntityNamingStrategy getNamingStrategy() { if (namingStrategy == null) { namingStrategy = new NamingStrategyWrapper( - NAMING_STRATEGY_PROVIDER.getPhysicalNamingStrategy(sessionFactoryName), getJdbcEnvironment()); + namingStrategyProvider.getPhysicalNamingStrategy(sessionFactoryName), getJdbcEnvironment()); } return namingStrategy; } @@ -282,4 +273,12 @@ public class GrailsDomainBinder implements AdditionalMappingContributor, TypeCon @Override public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {} + + /** + * Manually triggers the contribution process. Useful for unit testing + * where the full Hibernate bootstrap is not invoked. + */ + public void contribute(InFlightMetadataCollector metadataCollector, HibernateMappingContext mappingContext) { + contribute(null, metadataCollector, null, getMetadataBuildingContext()); + } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/connections/HibernateConnectionSourceFactory.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/connections/HibernateConnectionSourceFactory.java index 1f3e0f3379..6b37b0c2f9 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/connections/HibernateConnectionSourceFactory.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/connections/HibernateConnectionSourceFactory.java @@ -138,7 +138,7 @@ public class HibernateConnectionSourceFactory if (hibernateSettings.getPackagesToScan() != null) configuration.scanPackages(hibernateSettings.getPackagesToScan()); - configureNamingStrategy(name, hibernateSettings); + configureNamingStrategy(name, configuration, hibernateSettings); ClosureEventTriggeringInterceptor eventTriggeringInterceptor = resolveEventTriggeringInterceptor(hibernateSettings.getClosureEventTriggeringInterceptorClass()); @@ -224,10 +224,14 @@ public class HibernateConnectionSourceFactory } private static void configureNamingStrategy( - String name, HibernateConnectionSourceSettings.HibernateSettings hibernateSettings) { + String name, + HibernateMappingContextConfiguration configuration, + HibernateConnectionSourceSettings.HibernateSettings hibernateSettings) { try { Class<? extends PhysicalNamingStrategy> namingStrategy = hibernateSettings.getNaming_strategy(); - if (namingStrategy != null) GrailsDomainBinder.configureNamingStrategy(name, namingStrategy); + if (namingStrategy != null) { + configuration.getNamingStrategyProvider().configureNamingStrategy(name, namingStrategy); + } } catch (Throwable e) { throw new ConfigurationException("Error configuring naming strategy: " + e.getMessage(), e); } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java index bc9ce6a421..63cac860c9 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java @@ -209,7 +209,8 @@ public class HibernateHqlQuery extends Query { delegate.setCacheable(false); } else { if (!args.containsKey(HibernateQueryArgument.CACHE.value())) { - org.grails.orm.hibernate.cfg.Mapping m = org.grails.orm.hibernate.cfg.MappingCacheHolder.getInstance() + org.grails.orm.hibernate.cfg.Mapping m = ((org.grails.orm.hibernate.cfg.HibernateMappingContext) getEntity().getMappingContext()) + .getMappingCacheHolder() .getMapping(getEntity().getJavaClass()); if (m != null && m.getCache() != null && m.getCache().getEnabled()) { delegate.setCacheable(true); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlListQueryBuilder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlListQueryBuilder.java index b22b7e1d3b..b182e0f0ff 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlListQueryBuilder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlListQueryBuilder.java @@ -27,6 +27,7 @@ import org.grails.datastore.mapping.model.PersistentEntity; import org.grails.datastore.mapping.model.PersistentProperty; import org.grails.datastore.mapping.model.types.Association; import org.grails.datastore.mapping.model.types.Embedded; +import org.grails.orm.hibernate.cfg.HibernateMappingContext; import org.grails.orm.hibernate.cfg.Mapping; import org.grails.orm.hibernate.cfg.MappingCacheHolder; @@ -101,7 +102,8 @@ public class HqlListQueryBuilder { clauses.add(orderClause(alias, sort, dir, ignoreCase && isStringProp(sort))); } else { // fall back to default mapping sort - Mapping m = MappingCacheHolder.getInstance().getMapping(entity.getJavaClass()); + MappingCacheHolder cacheHolder = ((HibernateMappingContext) entity.getMappingContext()).getMappingCacheHolder(); + Mapping m = cacheHolder.getMapping(entity.getJavaClass()); if (m != null) { m.getSort() .getNamesAndDirections()
