diff --git a/.agents/skills/grails-developer/SKILL.md b/.agents/skills/grails-developer/SKILL.md
index 3f7ff29974d..00a6728513f 100644
--- a/.agents/skills/grails-developer/SKILL.md
+++ b/.agents/skills/grails-developer/SKILL.md
@@ -46,8 +46,8 @@ Activate this skill when developing with Grails 7, including:
## Technology Stack
Grails 7 is built on:
-- **Spring Boot**: 3.5.x
-- **Spring Framework**: 6.2.x
+- **Spring Boot**: 4.0.x
+- **Spring Framework**: 7.0.x
- **Groovy**: 4.0.x
- **Gradle**: 8.14.x
- **Spock**: 2.3-groovy-4.0
diff --git a/AGENTS.md b/AGENTS.md
index 4905df60ff2..5e76b8346a5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -73,8 +73,8 @@ export GRADLE_OPTS="-Xms2G -Xmx5G"
|-----------|---------|
| JDK | 17+ (baseline 17) |
| Groovy | 4.0.x |
-| Spring Boot | 3.5.x |
-| Spring Framework | 6.2.x |
+| Spring Boot | 4.0.x |
+| Spring Framework | 7.0.x |
| Spock | 2.3-groovy-4.0 |
| Gradle | 8.14.x |
| Jakarta EE | 10 |
diff --git a/NOTICE b/NOTICE
index 8d9515b5afd..5a32f68c219 100644
--- a/NOTICE
+++ b/NOTICE
@@ -22,4 +22,10 @@ may be obtained from:
http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/index.html
This product includes software developed by the OpenSymphony Group (http://www.opensymphony.com/). It uses Sitemesh2,
-licensed under the OpenSymphony Software License, Version 1.1. See licenses/LICENSE-opensymphony.txt for the full license terms.
\ No newline at end of file
+licensed under the OpenSymphony Software License, Version 1.1. See licenses/LICENSE-opensymphony.txt for the full license terms.
+
+Spring Framework ORM Hibernate 5 Support
+This product includes software from the Spring Framework project (https://spring.io/projects/spring-framework),
+vendored from Spring Framework 6.2.x. These classes were removed in Spring Framework 7.0.
+Copyright 2002-2024 the original author or authors.
+Licensed under the Apache License, Version 2.0.
diff --git a/build-logic/docs-core/build.gradle b/build-logic/docs-core/build.gradle
index 856409ccdf8..f885050bbdd 100644
--- a/build-logic/docs-core/build.gradle
+++ b/build-logic/docs-core/build.gradle
@@ -58,7 +58,7 @@ dependencies {
testImplementation "org.codehaus.groovy:groovy-test-junit5:${GroovySystem.version}"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.12.2'
- testImplementation 'org.junit.platform:junit-platform-runner:1.12.2'
+ testImplementation 'org.junit.platform:junit-platform-suite:1.12.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.12.2'
}
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy
index 9839e43ec7d..1ccaf89dee1 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy
@@ -114,6 +114,10 @@ class SbomPlugin implements Plugin
This class is registered via {@code META-INF/spring.factories} under the - * {@code org.springframework.boot.BootstrapRegistryInitializer} key.
+ * {@code org.springframework.boot.bootstrap.BootstrapRegistryInitializer} key. * * @since 7.1 */ diff --git a/grails-core/src/main/groovy/org/grails/compiler/injection/ApplicationClassInjector.groovy b/grails-core/src/main/groovy/org/grails/compiler/injection/ApplicationClassInjector.groovy index 9279072fb6b..2d126792dd6 100644 --- a/grails-core/src/main/groovy/org/grails/compiler/injection/ApplicationClassInjector.groovy +++ b/grails-core/src/main/groovy/org/grails/compiler/injection/ApplicationClassInjector.groovy @@ -62,9 +62,9 @@ class ApplicationClassInjector implements GrailsArtefactClassInjector { public static final String EXCLUDE_MEMBER = 'exclude' public static final ListWraps the Hibernate Criteria API in a builder. The builder can be retrieved through the "createCriteria()" dynamic static
diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTemplate.java b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTemplate.java
index 84366ff9211..1df7d1ad09a 100644
--- a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTemplate.java
+++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTemplate.java
@@ -65,12 +65,13 @@
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLExceptionTranslator;
-import org.springframework.orm.hibernate5.SessionFactoryUtils;
-import org.springframework.orm.hibernate5.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
+import org.grails.orm.hibernate.support.hibernate5.SessionFactoryUtils;
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder;
+
public class GrailsHibernateTemplate implements IHibernateTemplate {
private static final Logger LOG = LoggerFactory.getLogger(GrailsHibernateTemplate.class);
diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTransactionManager.groovy b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTransactionManager.groovy
index cf177896b4b..00fd6838459 100644
--- a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTransactionManager.groovy
+++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsHibernateTransactionManager.groovy
@@ -29,8 +29,8 @@ import org.hibernate.SessionFactory
import org.hibernate.engine.jdbc.spi.JdbcCoordinator
import org.hibernate.engine.spi.SessionImplementor
-import org.springframework.orm.hibernate5.HibernateTransactionManager
-import org.springframework.orm.hibernate5.SessionHolder
+import org.grails.orm.hibernate.support.hibernate5.HibernateTransactionManager
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder
import org.springframework.transaction.TransactionDefinition
import org.springframework.transaction.support.DefaultTransactionStatus
import org.springframework.transaction.support.TransactionSynchronizationManager
diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsSessionContext.java b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsSessionContext.java
index 648d8a43328..7f4943667c1 100644
--- a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsSessionContext.java
+++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/GrailsSessionContext.java
@@ -34,14 +34,15 @@
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.orm.hibernate5.SessionHolder;
-import org.springframework.orm.hibernate5.SpringFlushSynchronization;
-import org.springframework.orm.hibernate5.SpringJtaSessionContext;
-import org.springframework.orm.hibernate5.SpringSessionSynchronization;
import org.springframework.transaction.jta.SpringJtaSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder;
+import org.grails.orm.hibernate.support.hibernate5.SpringFlushSynchronization;
+import org.grails.orm.hibernate.support.hibernate5.SpringJtaSessionContext;
+import org.grails.orm.hibernate.support.hibernate5.SpringSessionSynchronization;
+
/**
* Based on org.springframework.orm.hibernate4.SpringSessionContext.
*
diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy
index 28a7c3bfa89..33724ab93ee 100644
--- a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy
+++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy
@@ -34,7 +34,7 @@ import org.hibernate.SessionFactory
import org.hibernate.query.Query
import org.springframework.core.convert.ConversionService
-import org.springframework.orm.hibernate5.SessionHolder
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionSynchronizationManager
diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateMappingContextSessionFactoryBean.java b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateMappingContextSessionFactoryBean.java
index d15e91f0088..29e969187a8 100644
--- a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateMappingContextSessionFactoryBean.java
+++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateMappingContextSessionFactoryBean.java
@@ -50,13 +50,13 @@
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
-import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
import org.grails.datastore.mapping.core.connections.ConnectionSource;
import org.grails.orm.hibernate.cfg.HibernateMappingContext;
import org.grails.orm.hibernate.cfg.HibernateMappingContextConfiguration;
+import org.grails.orm.hibernate.support.hibernate5.HibernateExceptionTranslator;
/**
* Configures a SessionFactory using a {@link org.grails.orm.hibernate.cfg.HibernateMappingContext} and a {@link org.grails.orm.hibernate.cfg.HibernateMappingContextConfiguration}
diff --git a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/HibernateOptimisticLockingSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/HibernateOptimisticLockingSpec.groovy
index a5a853a5b53..5d15897f773 100644
--- a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/HibernateOptimisticLockingSpec.groovy
+++ b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/HibernateOptimisticLockingSpec.groovy
@@ -22,7 +22,7 @@ import org.apache.grails.data.testing.tck.domains.OptLockNotVersioned
import org.apache.grails.data.testing.tck.domains.OptLockVersioned
import org.apache.grails.data.hibernate5.core.GrailsDataHibernate5TckManager
import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec
-import org.springframework.orm.hibernate5.HibernateOptimisticLockingFailureException
+import org.grails.orm.hibernate.support.hibernate5.HibernateOptimisticLockingFailureException
/**
* @author Burt Beckwith
diff --git a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/WithNewSessionAndExistingTransactionSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/WithNewSessionAndExistingTransactionSpec.groovy
index dd4c8c3c706..40815ce98d7 100644
--- a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/WithNewSessionAndExistingTransactionSpec.groovy
+++ b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/WithNewSessionAndExistingTransactionSpec.groovy
@@ -23,7 +23,7 @@ import org.apache.grails.data.hibernate5.core.GrailsDataHibernate5TckManager
import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec
import org.grails.orm.hibernate.HibernateDatastore
import org.hibernate.Session
-import org.springframework.orm.hibernate5.SessionHolder
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.TransactionSynchronizationManager
import spock.lang.Issue
diff --git a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/validation/BeanValidationSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/validation/BeanValidationSpec.groovy
index 5bd1fdd59bf..b844a2f8a9f 100644
--- a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/validation/BeanValidationSpec.groovy
+++ b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/tests/validation/BeanValidationSpec.groovy
@@ -22,12 +22,12 @@ package grails.gorm.tests.validation
import grails.gorm.annotation.Entity
import grails.gorm.transactions.Rollback
import org.grails.orm.hibernate.HibernateDatastore
-import org.hibernate.validator.constraints.NotBlank
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
import jakarta.validation.constraints.Digits
+import jakarta.validation.constraints.NotBlank
/**
* Created by graemerocher on 07/04/2017.
diff --git a/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy b/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy
index d8666d94149..e71e88bd592 100644
--- a/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy
+++ b/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy
@@ -35,8 +35,8 @@ import org.hibernate.SessionFactory
import org.hibernate.dialect.H2Dialect
import org.springframework.beans.factory.DisposableBean
import org.springframework.context.ApplicationContext
-import org.springframework.orm.hibernate5.SessionFactoryUtils
-import org.springframework.orm.hibernate5.SessionHolder
+import org.grails.orm.hibernate.support.hibernate5.SessionFactoryUtils
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.DefaultTransactionDefinition
import org.springframework.transaction.support.TransactionSynchronizationManager
diff --git a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/SchemaMultiTenantSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/SchemaMultiTenantSpec.groovy
index 701700e0f0c..599181c7fa9 100644
--- a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/SchemaMultiTenantSpec.groovy
+++ b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/SchemaMultiTenantSpec.groovy
@@ -27,7 +27,7 @@ import org.grails.orm.hibernate.HibernateDatastore
import org.hibernate.Session
import org.hibernate.dialect.H2Dialect
import org.hibernate.resource.jdbc.spi.JdbcSessionOwner
-import org.springframework.orm.hibernate5.SessionHolder
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder
import org.springframework.transaction.support.TransactionSynchronizationManager
import spock.lang.AutoCleanup
import spock.util.environment.RestoreSystemProperties
diff --git a/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/GrailsOpenSessionInViewInterceptor.java b/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/GrailsOpenSessionInViewInterceptor.java
index 782e1710a65..20fa6a1085c 100644
--- a/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/GrailsOpenSessionInViewInterceptor.java
+++ b/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/GrailsOpenSessionInViewInterceptor.java
@@ -27,9 +27,6 @@
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.orm.hibernate5.SessionFactoryUtils;
-import org.springframework.orm.hibernate5.SessionHolder;
-import org.springframework.orm.hibernate5.support.OpenSessionInViewInterceptor;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
@@ -38,6 +35,9 @@
import org.grails.orm.hibernate.AbstractHibernateDatastore;
import org.grails.orm.hibernate.HibernateDatastore;
import org.grails.orm.hibernate.connections.HibernateConnectionSourceSettings;
+import org.grails.orm.hibernate.support.hibernate5.SessionFactoryUtils;
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder;
+import org.grails.orm.hibernate.support.hibernate5.support.OpenSessionInViewInterceptor;
/**
* Extends the default Spring OSIV to support multiple datasources.
diff --git a/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/HibernatePersistenceContextInterceptor.java b/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/HibernatePersistenceContextInterceptor.java
index 1dad3f08536..18133ec1c86 100644
--- a/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/HibernatePersistenceContextInterceptor.java
+++ b/grails-data-hibernate5/grails-plugin/src/main/groovy/org/grails/plugin/hibernate/support/HibernatePersistenceContextInterceptor.java
@@ -31,8 +31,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.orm.hibernate5.SessionFactoryUtils;
-import org.springframework.orm.hibernate5.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import grails.persistence.support.PersistenceContextInterceptor;
@@ -41,6 +39,8 @@
import org.grails.datastore.mapping.core.connections.ConnectionSource;
import org.grails.orm.hibernate.AbstractHibernateDatastore;
import org.grails.orm.hibernate.support.HibernateRuntimeUtils;
+import org.grails.orm.hibernate.support.hibernate5.SessionFactoryUtils;
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder;
/**
* @author Graeme Rocher
diff --git a/grails-data-hibernate5/grails-plugin/src/test/groovy/org/grails/plugin/hibernate/support/MultiDataSourceSessionSpec.groovy b/grails-data-hibernate5/grails-plugin/src/test/groovy/org/grails/plugin/hibernate/support/MultiDataSourceSessionSpec.groovy
index 8b9e07513d7..16ed354f357 100644
--- a/grails-data-hibernate5/grails-plugin/src/test/groovy/org/grails/plugin/hibernate/support/MultiDataSourceSessionSpec.groovy
+++ b/grails-data-hibernate5/grails-plugin/src/test/groovy/org/grails/plugin/hibernate/support/MultiDataSourceSessionSpec.groovy
@@ -24,7 +24,7 @@ import org.grails.orm.hibernate.HibernateDatastore
import org.hibernate.Session
import org.hibernate.SessionFactory
import org.hibernate.dialect.H2Dialect
-import org.springframework.orm.hibernate5.SessionHolder
+import org.grails.orm.hibernate.support.hibernate5.SessionHolder
import org.springframework.transaction.support.TransactionSynchronizationManager
import org.springframework.web.context.request.WebRequest
import spock.lang.AutoCleanup
diff --git a/grails-data-hibernate5/spring-orm/build.gradle b/grails-data-hibernate5/spring-orm/build.gradle
new file mode 100644
index 00000000000..bc8b244ab86
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/build.gradle
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+// Vendored Spring Framework ORM Hibernate 5 integration classes.
+// These classes were removed in Spring Framework 7.0 and are vendored here
+// from Spring Framework 6.2.x (org.springframework.orm.hibernate5 package).
+// Original source: https://github.com/spring-projects/spring-framework/tree/v6.2.0/spring-orm/src/main/java/org/springframework/orm/hibernate5
+
+plugins {
+ id 'java-library'
+ id 'org.apache.grails.buildsrc.properties'
+ id 'org.apache.grails.buildsrc.compile'
+ id 'org.apache.grails.buildsrc.publish'
+ id 'org.apache.grails.buildsrc.sbom'
+}
+
+version = projectVersion
+group = 'org.apache.grails.data'
+
+ext {
+ pomTitle = 'Grails GORM ORM Hibernate 5 Support'
+ pomDescription = 'Vendored Spring Framework ORM Hibernate 5 integration classes for Grails GORM'
+}
+
+dependencies {
+ implementation platform(project(':grails-bom'))
+
+ api 'org.slf4j:slf4j-api'
+ api 'org.springframework:spring-orm'
+ api 'org.springframework:spring-web'
+ api 'org.springframework:spring-tx'
+ api 'org.springframework:spring-beans'
+ api 'org.springframework:spring-context'
+ compileOnly 'jakarta.servlet:jakarta.servlet-api'
+ api "org.hibernate:hibernate-core-jakarta:${hibernate5Version}", {
+ exclude group: 'commons-logging', module: 'commons-logging'
+ exclude group: 'org.slf4j', module: 'slf4j-api'
+ }
+}
+
+// Javadoc references Spring Framework internals not on our classpath - suppress errors for vendored code
+tasks.withType(Javadoc).configureEach {
+ options.addStringOption('Xdoclint:none', '-quiet')
+ failOnError = false
+}
+
+
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/ConfigurableJtaPlatform.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/ConfigurableJtaPlatform.java
new file mode 100644
index 00000000000..6ff1fa3ba13
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/ConfigurableJtaPlatform.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import jakarta.transaction.Status;
+import jakarta.transaction.Synchronization;
+import jakarta.transaction.SystemException;
+import jakarta.transaction.Transaction;
+import jakarta.transaction.TransactionManager;
+import jakarta.transaction.TransactionSynchronizationRegistry;
+import jakarta.transaction.UserTransaction;
+import org.hibernate.TransactionException;
+import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
+
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.jta.UserTransactionAdapter;
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of Hibernate 5's JtaPlatform SPI, exposing passed-in {@link TransactionManager},
+ * {@link UserTransaction} and {@link TransactionSynchronizationRegistry} references.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ */
+@SuppressWarnings("serial")
+class ConfigurableJtaPlatform implements JtaPlatform {
+
+ private final TransactionManager transactionManager;
+
+ private final UserTransaction userTransaction;
+
+ @Nullable
+ private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
+
+
+ /**
+ * Create a new ConfigurableJtaPlatform instance with the given
+ * JTA TransactionManager and optionally a given UserTransaction.
+ * @param tm the JTA TransactionManager reference (required)
+ * @param ut the JTA UserTransaction reference (optional)
+ * @param tsr the JTA 1.1 TransactionSynchronizationRegistry (optional)
+ */
+ public ConfigurableJtaPlatform(TransactionManager tm, @Nullable UserTransaction ut,
+ @Nullable TransactionSynchronizationRegistry tsr) {
+
+ Assert.notNull(tm, "TransactionManager reference must not be null");
+ this.transactionManager = tm;
+ this.userTransaction = (ut != null ? ut : new UserTransactionAdapter(tm));
+ this.transactionSynchronizationRegistry = tsr;
+ }
+
+
+ @Override
+ public TransactionManager retrieveTransactionManager() {
+ return this.transactionManager;
+ }
+
+ @Override
+ public UserTransaction retrieveUserTransaction() {
+ return this.userTransaction;
+ }
+
+ @Override
+ public Object getTransactionIdentifier(Transaction transaction) {
+ return transaction;
+ }
+
+ @Override
+ public boolean canRegisterSynchronization() {
+ try {
+ return (this.transactionManager.getStatus() == Status.STATUS_ACTIVE);
+ }
+ catch (SystemException ex) {
+ throw new TransactionException("Could not determine JTA transaction status", ex);
+ }
+ }
+
+ @Override
+ public void registerSynchronization(Synchronization synchronization) {
+ if (this.transactionSynchronizationRegistry != null) {
+ this.transactionSynchronizationRegistry.registerInterposedSynchronization(synchronization);
+ }
+ else {
+ try {
+ this.transactionManager.getTransaction().registerSynchronization(synchronization);
+ }
+ catch (Exception ex) {
+ throw new TransactionException("Could not access JTA Transaction to register synchronization", ex);
+ }
+ }
+ }
+
+ @Override
+ public int getCurrentStatus() throws SystemException {
+ return this.transactionManager.getStatus();
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateCallback.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateCallback.java
new file mode 100644
index 00000000000..dcd07a641e7
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateCallback.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * Callback interface for Hibernate code. To be used with {@link HibernateTemplate}'s
+ * execution methods, often as anonymous classes within a method implementation.
+ * A typical implementation will call {@code Session.load/find/update} to perform
+ * some operations on persistent objects.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @param Allows for returning a result object created within the callback,
+ * i.e. a domain object or a collection of domain objects.
+ * A thrown custom RuntimeException is treated as an application exception:
+ * It gets propagated to the caller of the template.
+ * @param session active Hibernate session
+ * @return a result object, or {@code null} if none
+ * @throws HibernateException if thrown by the Hibernate API
+ * @see HibernateTemplate#execute
+ */
+ @Nullable
+ T doInHibernate(Session session) throws HibernateException;
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateExceptionTranslator.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateExceptionTranslator.java
new file mode 100644
index 00000000000..86dd9fb91a2
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateExceptionTranslator.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import jakarta.persistence.PersistenceException;
+import org.hibernate.HibernateException;
+import org.hibernate.JDBCException;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.jdbc.support.SQLExceptionTranslator;
+import org.springframework.lang.Nullable;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
+
+/**
+ * {@link PersistenceExceptionTranslator} capable of translating {@link HibernateException}
+ * instances to Spring's {@link DataAccessException} hierarchy. As of Spring 4.3.2 and
+ * Hibernate 5.2, it also converts standard JPA {@link PersistenceException} instances.
+ *
+ * Extended by {@link LocalSessionFactoryBean}, so there is no need to declare this
+ * translator in addition to a {@code LocalSessionFactoryBean}.
+ *
+ * When configuring the container with {@code @Configuration} classes, a {@code @Bean}
+ * of this type must be registered manually.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
+ * @see SessionFactoryUtils#convertHibernateAccessException(HibernateException)
+ * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible(RuntimeException)
+ */
+public class HibernateExceptionTranslator implements PersistenceExceptionTranslator {
+
+ @Nullable
+ private SQLExceptionTranslator jdbcExceptionTranslator;
+
+
+ /**
+ * Set the JDBC exception translator for Hibernate exception translation purposes.
+ * Applied to any detected {@link java.sql.SQLException} root cause of a Hibernate
+ * {@link JDBCException}, overriding Hibernate's own {@code SQLException} translation
+ * (which is based on a Hibernate Dialect for a specific target database).
+ * @since 5.1
+ * @see java.sql.SQLException
+ * @see org.hibernate.JDBCException
+ * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
+ * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
+ */
+ public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
+ this.jdbcExceptionTranslator = jdbcExceptionTranslator;
+ }
+
+
+ @Override
+ @Nullable
+ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
+ if (ex instanceof HibernateException hibernateEx) {
+ return convertHibernateAccessException(hibernateEx);
+ }
+ if (ex instanceof PersistenceException) {
+ if (ex.getCause() instanceof HibernateException hibernateEx) {
+ return convertHibernateAccessException(hibernateEx);
+ }
+ return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
+ }
+ return null;
+ }
+
+ /**
+ * Convert the given HibernateException to an appropriate exception from the
+ * {@code org.springframework.dao} hierarchy.
+ * Will automatically apply a specified SQLExceptionTranslator to a
+ * Hibernate JDBCException, otherwise rely on Hibernate's default translation.
+ * @param ex the HibernateException that occurred
+ * @return a corresponding DataAccessException
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+ protected DataAccessException convertHibernateAccessException(HibernateException ex) {
+ if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException jdbcEx) {
+ DataAccessException dae = this.jdbcExceptionTranslator.translate(
+ "Hibernate operation: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
+ if (dae != null) {
+ return dae;
+ }
+ }
+ return SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateJdbcException.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateJdbcException.java
new file mode 100644
index 00000000000..479e126a03e
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateJdbcException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import java.sql.SQLException;
+
+import org.hibernate.JDBCException;
+
+import org.springframework.dao.UncategorizedDataAccessException;
+import org.springframework.lang.Nullable;
+
+/**
+ * Hibernate-specific subclass of UncategorizedDataAccessException,
+ * for JDBC exceptions that Hibernate wrapped.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+@SuppressWarnings("serial")
+public class HibernateJdbcException extends UncategorizedDataAccessException {
+
+ public HibernateJdbcException(JDBCException ex) {
+ super("JDBC exception on Hibernate data access: SQLException for SQL [" + ex.getSQL() + "]; SQL state [" +
+ ex.getSQLState() + "]; error code [" + ex.getErrorCode() + "]; " + ex.getMessage(), ex);
+ }
+
+ /**
+ * Return the underlying SQLException.
+ */
+ @SuppressWarnings("NullAway")
+ public SQLException getSQLException() {
+ return ((JDBCException) getCause()).getSQLException();
+ }
+
+ /**
+ * Return the SQL that led to the problem.
+ */
+ @Nullable
+ @SuppressWarnings("NullAway")
+ public String getSql() {
+ return ((JDBCException) getCause()).getSQL();
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateObjectRetrievalFailureException.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateObjectRetrievalFailureException.java
new file mode 100644
index 00000000000..701f815cc56
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateObjectRetrievalFailureException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import org.hibernate.HibernateException;
+import org.hibernate.UnresolvableObjectException;
+import org.hibernate.WrongClassException;
+
+import org.springframework.lang.Nullable;
+import org.springframework.orm.ObjectRetrievalFailureException;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Hibernate-specific subclass of ObjectRetrievalFailureException.
+ * Converts Hibernate's UnresolvableObjectException and WrongClassException.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+@SuppressWarnings("serial")
+public class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException {
+
+ public HibernateObjectRetrievalFailureException(UnresolvableObjectException ex) {
+ super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex);
+ }
+
+ public HibernateObjectRetrievalFailureException(WrongClassException ex) {
+ super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex);
+ }
+
+
+ @Nullable
+ static Object getIdentifier(HibernateException hibEx) {
+ try {
+ // getIdentifier declares Serializable return value on 5.x but Object on 6.x
+ // -> not binary compatible, let's invoke it reflectively for the time being
+ return ReflectionUtils.invokeMethod(hibEx.getClass().getMethod("getIdentifier"), hibEx);
+ }
+ catch (NoSuchMethodException ex) {
+ return null;
+ }
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateOperations.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateOperations.java
new file mode 100644
index 00000000000..295f48c993e
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateOperations.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.hibernate.Filter;
+import org.hibernate.LockMode;
+import org.hibernate.ReplicationMode;
+import org.hibernate.criterion.DetachedCriteria;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.lang.Nullable;
+
+/**
+ * Interface that specifies a common set of Hibernate operations as well as
+ * a general {@link #execute} method for Session-based lambda expressions.
+ * Implemented by {@link HibernateTemplate}. Not often used, but a useful option
+ * to enhance testability, as it can easily be mocked or stubbed.
+ *
+ * Defines {@code HibernateTemplate}'s data access methods that mirror various
+ * {@link org.hibernate.Session} methods. Users are strongly encouraged to read the
+ * Hibernate {@code Session} javadocs for details on the semantics of those methods.
+ *
+ * A deprecation note: While {@link HibernateTemplate} and this operations
+ * interface are being kept around for backwards compatibility in terms of the data
+ * access implementation style in Spring applications, we strongly recommend the use
+ * of native {@link org.hibernate.Session} access code for non-trivial interactions.
+ * This in particular affects parameterized queries where - on Java 8+ - a custom
+ * {@link HibernateCallback} lambda code block with {@code createQuery} and several
+ * {@code setParameter} calls on the {@link org.hibernate.query.Query} interface
+ * is an elegant solution, to be executed via the general {@link #execute} method.
+ * All such operations which benefit from a lambda variant have been marked as
+ * {@code deprecated} on this interface.
+ *
+ * A Hibernate compatibility note: {@link HibernateTemplate} and the
+ * operations on this interface generally aim to be applicable across all Hibernate
+ * versions. In terms of binary compatibility, Spring ships a variant for each major
+ * generation of Hibernate (in the present case: Hibernate ORM 5.x). However, due to
+ * refactorings and removals in Hibernate ORM 5.3, some variants - in particular
+ * legacy positional parameters starting from index 0 - do not work anymore.
+ * All affected operations are marked as deprecated; please replace them with the
+ * general {@link #execute} method and custom lambda blocks creating the queries,
+ * ideally setting named parameters through {@link org.hibernate.query.Query}.
+ * Please be aware that deprecated operations are known to work with Hibernate
+ * ORM 5.2 but may not work with Hibernate ORM 5.3 and higher anymore.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see HibernateTemplate
+ * @see org.hibernate.Session
+ * @see HibernateTransactionManager
+ */
+public interface HibernateOperations {
+
+ /**
+ * Execute the action specified by the given action object within a
+ * {@link org.hibernate.Session}.
+ * Application exceptions thrown by the action object get propagated
+ * to the caller (can only be unchecked). Hibernate exceptions are
+ * transformed into appropriate DAO ones. Allows for returning a result
+ * object, that is a domain object or a collection of domain objects.
+ * Note: Callback code is not supposed to handle transactions itself!
+ * Use an appropriate transaction manager like
+ * {@link HibernateTransactionManager}. Generally, callback code must not
+ * touch any {@code Session} lifecycle methods, like close,
+ * disconnect, or reconnect, to let the template do its work.
+ * @param action callback object that specifies the Hibernate action
+ * @return a result object returned by the action, or {@code null}
+ * @throws DataAccessException in case of Hibernate errors
+ * @see HibernateTransactionManager
+ * @see org.hibernate.Session
+ */
+ @Nullable
+ This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(Class, Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityClass a persistent class
+ * @param id the identifier of the persistent instance
+ * @return the persistent instance, or {@code null} if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, Serializable)
+ */
+ @Nullable
+ Obtains the specified lock mode if the instance exists.
+ * This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(Class, Serializable, LockMode)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityClass a persistent class
+ * @param id the identifier of the persistent instance
+ * @param lockMode the lock mode to obtain
+ * @return the persistent instance, or {@code null} if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, Serializable, LockMode)
+ */
+ @Nullable
+ This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(String, Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityName the name of the persistent entity
+ * @param id the identifier of the persistent instance
+ * @return the persistent instance, or {@code null} if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, Serializable)
+ */
+ @Nullable
+ Object get(String entityName, Serializable id) throws DataAccessException;
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, or {@code null} if not found.
+ * Obtains the specified lock mode if the instance exists.
+ * This method is a thin wrapper around
+ * {@link org.hibernate.Session#get(String, Serializable, LockMode)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityName the name of the persistent entity
+ * @param id the identifier of the persistent instance
+ * @param lockMode the lock mode to obtain
+ * @return the persistent instance, or {@code null} if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#get(Class, Serializable, LockMode)
+ */
+ @Nullable
+ Object get(String entityName, Serializable id, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, throwing an exception if not found.
+ * This method is a thin wrapper around
+ * {@link org.hibernate.Session#load(Class, Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityClass a persistent class
+ * @param id the identifier of the persistent instance
+ * @return the persistent instance
+ * @throws org.springframework.orm.ObjectRetrievalFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#load(Class, Serializable)
+ */
+ This method is a thin wrapper around
+ * {@link org.hibernate.Session#load(Class, Serializable, LockMode)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityClass a persistent class
+ * @param id the identifier of the persistent instance
+ * @param lockMode the lock mode to obtain
+ * @return the persistent instance
+ * @throws org.springframework.orm.ObjectRetrievalFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#load(Class, Serializable)
+ */
+ This method is a thin wrapper around
+ * {@link org.hibernate.Session#load(String, Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityName the name of the persistent entity
+ * @param id the identifier of the persistent instance
+ * @return the persistent instance
+ * @throws org.springframework.orm.ObjectRetrievalFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#load(Class, Serializable)
+ */
+ Object load(String entityName, Serializable id) throws DataAccessException;
+
+ /**
+ * Return the persistent instance of the given entity class
+ * with the given identifier, throwing an exception if not found.
+ * Obtains the specified lock mode if the instance exists.
+ * This method is a thin wrapper around
+ * {@link org.hibernate.Session#load(String, Serializable, LockMode)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entityName the name of the persistent entity
+ * @param id the identifier of the persistent instance
+ * @param lockMode the lock mode to obtain
+ * @return the persistent instance
+ * @throws org.springframework.orm.ObjectRetrievalFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#load(Class, Serializable)
+ */
+ Object load(String entityName, Serializable id, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Return all persistent instances of the given entity class.
+ * Note: Use queries or criteria for retrieving a specific subset.
+ * @param entityClass a persistent class
+ * @return a {@link List} containing 0 or more persistent instances
+ * @throws DataAccessException if there is a Hibernate error
+ * @see org.hibernate.Session#createCriteria
+ */
+ This method is a thin wrapper around
+ * {@link org.hibernate.Session#load(Object, Serializable)} for convenience.
+ * For an explanation of the exact semantics of this method, please do refer to
+ * the Hibernate API documentation in the first instance.
+ * @param entity the object (of the target class) to load into
+ * @param id the identifier of the persistent instance
+ * @throws org.springframework.orm.ObjectRetrievalFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#load(Object, Serializable)
+ */
+ void load(Object entity, Serializable id) throws DataAccessException;
+
+ /**
+ * Re-read the state of the given persistent instance.
+ * @param entity the persistent instance to re-read
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#refresh(Object)
+ */
+ void refresh(Object entity) throws DataAccessException;
+
+ /**
+ * Re-read the state of the given persistent instance.
+ * Obtains the specified lock mode for the instance.
+ * @param entity the persistent instance to re-read
+ * @param lockMode the lock mode to obtain
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#refresh(Object, LockMode)
+ */
+ void refresh(Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Check whether the given object is in the Session cache.
+ * @param entity the persistence instance to check
+ * @return whether the given object is in the Session cache
+ * @throws DataAccessException if there is a Hibernate error
+ * @see org.hibernate.Session#contains
+ */
+ boolean contains(Object entity) throws DataAccessException;
+
+ /**
+ * Remove the given object from the {@link org.hibernate.Session} cache.
+ * @param entity the persistent instance to evict
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#evict
+ */
+ void evict(Object entity) throws DataAccessException;
+
+ /**
+ * Force initialization of a Hibernate proxy or persistent collection.
+ * @param proxy a proxy for a persistent object or a persistent collection
+ * @throws DataAccessException if we can't initialize the proxy, for example
+ * because it is not associated with an active Session
+ * @see org.hibernate.Hibernate#initialize
+ */
+ void initialize(Object proxy) throws DataAccessException;
+
+ /**
+ * Return an enabled Hibernate {@link Filter} for the given filter name.
+ * The returned {@code Filter} instance can be used to set filter parameters.
+ * @param filterName the name of the filter
+ * @return the enabled Hibernate {@code Filter} (either already
+ * enabled or enabled on the fly by this operation)
+ * @throws IllegalStateException if we are not running within a
+ * transactional Session (in which case this operation does not make sense)
+ */
+ Filter enableFilter(String filterName) throws IllegalStateException;
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for storing individual objects
+ //-------------------------------------------------------------------------
+
+ /**
+ * Obtain the specified lock level upon the given object, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entity the persistent instance to lock
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#lock(Object, LockMode)
+ */
+ void lock(Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Obtain the specified lock level upon the given object, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to lock
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#lock(String, Object, LockMode)
+ */
+ void lock(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance.
+ * @param entity the transient instance to persist
+ * @return the generated identifier
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#save(Object)
+ */
+ Serializable save(Object entity) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance.
+ * @param entityName the name of the persistent entity
+ * @param entity the transient instance to persist
+ * @return the generated identifier
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#save(String, Object)
+ */
+ Serializable save(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Update the given persistent instance,
+ * associating it with the current Hibernate {@link org.hibernate.Session}.
+ * @param entity the persistent instance to update
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#update(Object)
+ */
+ void update(Object entity) throws DataAccessException;
+
+ /**
+ * Update the given persistent instance,
+ * associating it with the current Hibernate {@link org.hibernate.Session}.
+ * Obtains the specified lock mode if the instance exists, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entity the persistent instance to update
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#update(Object)
+ */
+ void update(Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Update the given persistent instance,
+ * associating it with the current Hibernate {@link org.hibernate.Session}.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to update
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#update(String, Object)
+ */
+ void update(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Update the given persistent instance,
+ * associating it with the current Hibernate {@link org.hibernate.Session}.
+ * Obtains the specified lock mode if the instance exists, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to update
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#update(String, Object)
+ */
+ void update(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Save or update the given persistent instance,
+ * according to its id (matching the configured "unsaved-value"?).
+ * Associates the instance with the current Hibernate {@link org.hibernate.Session}.
+ * @param entity the persistent instance to save or update
+ * (to be associated with the Hibernate {@code Session})
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#saveOrUpdate(Object)
+ */
+ void saveOrUpdate(Object entity) throws DataAccessException;
+
+ /**
+ * Save or update the given persistent instance,
+ * according to its id (matching the configured "unsaved-value"?).
+ * Associates the instance with the current Hibernate {@code Session}.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to save or update
+ * (to be associated with the Hibernate {@code Session})
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#saveOrUpdate(String, Object)
+ */
+ void saveOrUpdate(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Persist the state of the given detached instance according to the
+ * given replication mode, reusing the current identifier value.
+ * @param entity the persistent object to replicate
+ * @param replicationMode the Hibernate ReplicationMode
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#replicate(Object, ReplicationMode)
+ */
+ void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException;
+
+ /**
+ * Persist the state of the given detached instance according to the
+ * given replication mode, reusing the current identifier value.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent object to replicate
+ * @param replicationMode the Hibernate ReplicationMode
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#replicate(String, Object, ReplicationMode)
+ */
+ void replicate(String entityName, Object entity, ReplicationMode replicationMode) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance. Follows JSR-220 semantics.
+ * Similar to {@code save}, associating the given object
+ * with the current Hibernate {@link org.hibernate.Session}.
+ * @param entity the persistent instance to persist
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#persist(Object)
+ * @see #save
+ */
+ void persist(Object entity) throws DataAccessException;
+
+ /**
+ * Persist the given transient instance. Follows JSR-220 semantics.
+ * Similar to {@code save}, associating the given object
+ * with the current Hibernate {@link org.hibernate.Session}.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to persist
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#persist(String, Object)
+ * @see #save
+ */
+ void persist(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Copy the state of the given object onto the persistent object
+ * with the same identifier. Follows JSR-220 semantics.
+ * Similar to {@code saveOrUpdate}, but never associates the given
+ * object with the current Hibernate Session. In case of a new entity,
+ * the state will be copied over as well.
+ * Note that {@code merge} will not update the identifiers
+ * in the passed-in object graph (in contrast to TopLink)! Consider
+ * registering Spring's {@code IdTransferringMergeEventListener} if
+ * you would like to have newly assigned ids transferred to the original
+ * object graph too.
+ * @param entity the object to merge with the corresponding persistence instance
+ * @return the updated, registered persistent instance
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#merge(Object)
+ * @see #saveOrUpdate
+ */
+ Similar to {@code saveOrUpdate}, but never associates the given
+ * object with the current Hibernate {@link org.hibernate.Session}. In
+ * the case of a new entity, the state will be copied over as well.
+ * Note that {@code merge} will not update the identifiers
+ * in the passed-in object graph (in contrast to TopLink)! Consider
+ * registering Spring's {@code IdTransferringMergeEventListener}
+ * if you would like to have newly assigned ids transferred to the
+ * original object graph too.
+ * @param entityName the name of the persistent entity
+ * @param entity the object to merge with the corresponding persistence instance
+ * @return the updated, registered persistent instance
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#merge(String, Object)
+ * @see #saveOrUpdate
+ */
+ Obtains the specified lock mode if the instance exists, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entity the persistent instance to delete
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#delete(Object)
+ */
+ void delete(Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Delete the given persistent instance.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to delete
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#delete(Object)
+ */
+ void delete(String entityName, Object entity) throws DataAccessException;
+
+ /**
+ * Delete the given persistent instance.
+ * Obtains the specified lock mode if the instance exists, implicitly
+ * checking whether the corresponding database entry still exists.
+ * @param entityName the name of the persistent entity
+ * @param entity the persistent instance to delete
+ * @param lockMode the lock mode to obtain
+ * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#delete(Object)
+ */
+ void delete(String entityName, Object entity, LockMode lockMode) throws DataAccessException;
+
+ /**
+ * Delete all given persistent instances.
+ * This can be combined with any of the find methods to delete by query
+ * in two lines of code.
+ * @param entities the persistent instances to delete
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#delete(Object)
+ */
+ void deleteAll(Collection> entities) throws DataAccessException;
+
+ /**
+ * Flush all pending saves, updates and deletes to the database.
+ * Only invoke this for selective eager flushing, for example when
+ * JDBC code needs to see certain changes within the same transaction.
+ * Else, it is preferable to rely on auto-flushing at transaction
+ * completion.
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#flush
+ */
+ void flush() throws DataAccessException;
+
+ /**
+ * Remove all objects from the {@link org.hibernate.Session} cache, and
+ * cancel all pending saves, updates and deletes.
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#clear
+ */
+ void clear() throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Convenience finder methods for detached criteria
+ //-------------------------------------------------------------------------
+
+ /**
+ * Execute a query based on a given Hibernate criteria object.
+ * @param criteria the detached Hibernate criteria object.
+ * Note: Do not reuse criteria objects! They need to recreated per execution,
+ * due to the suboptimal design of Hibernate's criteria facility.
+ * @return a {@link List} containing 0 or more persistent instances
+ * @throws DataAccessException in case of Hibernate errors
+ * @see DetachedCriteria#getExecutableCriteria(org.hibernate.Session)
+ */
+ List> findByCriteria(DetachedCriteria criteria) throws DataAccessException;
+
+ /**
+ * Execute a query based on the given Hibernate criteria object.
+ * @param criteria the detached Hibernate criteria object.
+ * Note: Do not reuse criteria objects! They need to recreated per execution,
+ * due to the suboptimal design of Hibernate's criteria facility.
+ * @param firstResult the index of the first result object to be retrieved
+ * (numbered from 0)
+ * @param maxResults the maximum number of result objects to retrieve
+ * (or <=0 for no limit)
+ * @return a {@link List} containing 0 or more persistent instances
+ * @throws DataAccessException in case of Hibernate errors
+ * @see DetachedCriteria#getExecutableCriteria(org.hibernate.Session)
+ * @see org.hibernate.Criteria#setFirstResult(int)
+ * @see org.hibernate.Criteria#setMaxResults(int)
+ */
+ List> findByCriteria(DetachedCriteria criteria, int firstResult, int maxResults) throws DataAccessException;
+
+ /**
+ * Execute a query based on the given example entity object.
+ * @param exampleEntity an instance of the desired entity,
+ * serving as example for "query-by-example"
+ * @return a {@link List} containing 0 or more persistent instances
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.criterion.Example#create(Object)
+ */
+ A named query is defined in a Hibernate mapping file.
+ * @param queryName the name of a Hibernate query in a mapping file
+ * @param values the values of the parameters
+ * @return a {@link List} containing the results of the query execution
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#getNamedQuery(String)
+ * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback}
+ * lambda code block passed to the general {@link #execute} method
+ */
+ @Deprecated
+ List> findByNamedQuery(String queryName, Object... values) throws DataAccessException;
+
+ /**
+ * Execute a named query, binding one value to a ":" named parameter
+ * in the query string.
+ * A named query is defined in a Hibernate mapping file.
+ * @param queryName the name of a Hibernate query in a mapping file
+ * @param paramName the name of parameter
+ * @param value the value of the parameter
+ * @return a {@link List} containing the results of the query execution
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#getNamedQuery(String)
+ * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback}
+ * lambda code block passed to the general {@link #execute} method
+ */
+ @Deprecated
+ List> findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
+ throws DataAccessException;
+
+ /**
+ * Execute a named query, binding a number of values to ":" named
+ * parameters in the query string.
+ * A named query is defined in a Hibernate mapping file.
+ * @param queryName the name of a Hibernate query in a mapping file
+ * @param paramNames the names of the parameters
+ * @param values the values of the parameters
+ * @return a {@link List} containing the results of the query execution
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#getNamedQuery(String)
+ * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback}
+ * lambda code block passed to the general {@link #execute} method
+ */
+ @Deprecated
+ List> findByNamedQueryAndNamedParam(String queryName, String[] paramNames, Object[] values)
+ throws DataAccessException;
+
+ /**
+ * Execute a named query, binding the properties of the given bean to
+ * ":" named parameters in the query string.
+ * A named query is defined in a Hibernate mapping file.
+ * @param queryName the name of a Hibernate query in a mapping file
+ * @param valueBean the values of the parameters
+ * @return a {@link List} containing the results of the query execution
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Query#setProperties
+ * @see org.hibernate.Session#getNamedQuery(String)
+ * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback}
+ * lambda code block passed to the general {@link #execute} method
+ */
+ @Deprecated
+ List> findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Convenience query methods for iteration and bulk updates/deletes
+ //-------------------------------------------------------------------------
+
+ /**
+ * Execute a query for persistent instances, binding a number of
+ * values to "?" parameters in the query string.
+ * Returns the results as an {@link Iterator}. Entities returned are
+ * initialized on demand. See the Hibernate API documentation for details.
+ * @param queryString a query expressed in Hibernate's query language
+ * @param values the values of the parameters
+ * @return an {@link Iterator} containing 0 or more persistent instances
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#createQuery
+ * @see org.hibernate.Query#iterate
+ * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback}
+ * lambda code block passed to the general {@link #execute} method
+ */
+ @Deprecated
+ Iterator> iterate(String queryString, Object... values) throws DataAccessException;
+
+ /**
+ * Immediately close an {@link Iterator} created by any of the various
+ * {@code iterate(..)} operations, instead of waiting until the
+ * session is closed or disconnected.
+ * @param it the {@code Iterator} to close
+ * @throws DataAccessException if the {@code Iterator} could not be closed
+ * @see org.hibernate.Hibernate#close
+ * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback}
+ * lambda code block passed to the general {@link #execute} method
+ */
+ @Deprecated
+ void closeIterator(Iterator> it) throws DataAccessException;
+
+ /**
+ * Update/delete all objects according to the given query, binding a number of
+ * values to "?" parameters in the query string.
+ * @param queryString an update/delete query expressed in Hibernate's query language
+ * @param values the values of the parameters
+ * @return the number of instances updated/deleted
+ * @throws DataAccessException in case of Hibernate errors
+ * @see org.hibernate.Session#createQuery
+ * @see org.hibernate.Query#executeUpdate
+ * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback}
+ * lambda code block passed to the general {@link #execute} method
+ */
+ @Deprecated
+ int bulkUpdate(String queryString, Object... values) throws DataAccessException;
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateOptimisticLockingFailureException.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateOptimisticLockingFailureException.java
new file mode 100644
index 00000000000..6390892c52e
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateOptimisticLockingFailureException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import org.hibernate.StaleObjectStateException;
+import org.hibernate.StaleStateException;
+import org.hibernate.dialect.lock.OptimisticEntityLockException;
+
+import org.springframework.orm.ObjectOptimisticLockingFailureException;
+
+/**
+ * Hibernate-specific subclass of ObjectOptimisticLockingFailureException.
+ * Converts Hibernate's StaleObjectStateException, StaleStateException
+ * and OptimisticEntityLockException.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+@SuppressWarnings("serial")
+public class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
+
+ public HibernateOptimisticLockingFailureException(StaleObjectStateException ex) {
+ super(ex.getEntityName(), HibernateObjectRetrievalFailureException.getIdentifier(ex), ex.getMessage(), ex);
+ }
+
+ public HibernateOptimisticLockingFailureException(StaleStateException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+ public HibernateOptimisticLockingFailureException(OptimisticEntityLockException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateQueryException.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateQueryException.java
new file mode 100644
index 00000000000..f1e7480464c
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateQueryException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import org.hibernate.QueryException;
+
+import org.springframework.dao.InvalidDataAccessResourceUsageException;
+import org.springframework.lang.Nullable;
+
+/**
+ * Hibernate-specific subclass of InvalidDataAccessResourceUsageException,
+ * thrown on invalid HQL query syntax.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+@SuppressWarnings("serial")
+public class HibernateQueryException extends InvalidDataAccessResourceUsageException {
+
+ public HibernateQueryException(QueryException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+ /**
+ * Return the HQL query string that was invalid.
+ */
+ @Nullable
+ public String getQueryString() {
+ QueryException cause = (QueryException) getCause();
+ return (cause != null ? cause.getQueryString() : null);
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateSystemException.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateSystemException.java
new file mode 100644
index 00000000000..be1cd04c3c8
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateSystemException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import org.hibernate.HibernateException;
+
+import org.springframework.dao.UncategorizedDataAccessException;
+import org.springframework.lang.Nullable;
+
+/**
+ * Hibernate-specific subclass of UncategorizedDataAccessException,
+ * for Hibernate system errors that do not match any concrete
+ * {@code org.springframework.dao} exceptions.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+@SuppressWarnings("serial")
+public class HibernateSystemException extends UncategorizedDataAccessException {
+
+ /**
+ * Create a new HibernateSystemException,
+ * wrapping an arbitrary HibernateException.
+ * @param cause the HibernateException thrown
+ */
+ public HibernateSystemException(@Nullable HibernateException cause) {
+ super(cause != null ? cause.getMessage() : null, cause);
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateTemplate.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateTemplate.java
new file mode 100644
index 00000000000..9d1b44520c0
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/HibernateTemplate.java
@@ -0,0 +1,1185 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import jakarta.persistence.PersistenceException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.Criteria;
+import org.hibernate.Filter;
+import org.hibernate.FlushMode;
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.LockMode;
+import org.hibernate.LockOptions;
+import org.hibernate.ReplicationMode;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.criterion.DetachedCriteria;
+import org.hibernate.criterion.Example;
+import org.hibernate.query.Query;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.support.ResourceHolderSupport;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class that simplifies Hibernate data access code. Automatically
+ * converts HibernateExceptions into DataAccessExceptions, following the
+ * {@code org.springframework.dao} exception hierarchy.
+ *
+ * The central method is {@code execute}, supporting Hibernate access code
+ * implementing the {@link HibernateCallback} interface. It provides Hibernate Session
+ * handling such that neither the HibernateCallback implementation nor the calling
+ * code needs to explicitly care about retrieving/closing Hibernate Sessions,
+ * or handling Session lifecycle exceptions. For typical single step actions,
+ * there are various convenience methods (find, load, saveOrUpdate, delete).
+ *
+ * Can be used within a service implementation via direct instantiation
+ * with a SessionFactory reference, or get prepared in an application context
+ * and given to services as bean reference. Note: The SessionFactory should
+ * always be configured as bean in the application context, in the first case
+ * given to the service directly, in the second case to the prepared template.
+ *
+ * NOTE: Hibernate access code can also be coded against the native Hibernate
+ * {@link Session}. Hence, for newly started projects, consider adopting the standard
+ * Hibernate style of coding against {@link SessionFactory#getCurrentSession()}.
+ * Alternatively, use {@link #execute(HibernateCallback)} with Java 8 lambda code blocks
+ * against the callback-provided {@code Session} which results in elegant code as well,
+ * decoupled from the Hibernate Session lifecycle. The remaining operations on this
+ * HibernateTemplate are deprecated in the meantime and primarily exist as a migration
+ * helper for older Hibernate 3.x/4.x data access code in existing applications.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see #setSessionFactory
+ * @see HibernateCallback
+ * @see Session
+ * @see LocalSessionFactoryBean
+ * @see HibernateTransactionManager
+ * @see org.springframework.orm.hibernate5.support.OpenSessionInViewFilter
+ * @see org.springframework.orm.hibernate5.support.OpenSessionInViewInterceptor
+ */
+public class HibernateTemplate implements HibernateOperations, InitializingBean {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ @Nullable
+ private SessionFactory sessionFactory;
+
+ @Nullable
+ private String[] filterNames;
+
+ private boolean exposeNativeSession = false;
+
+ private boolean checkWriteOperations = true;
+
+ private boolean cacheQueries = false;
+
+ @Nullable
+ private String queryCacheRegion;
+
+ private int fetchSize = 0;
+
+ private int maxResults = 0;
+
+
+ /**
+ * Create a new HibernateTemplate instance.
+ */
+ public HibernateTemplate() {
+ }
+
+ /**
+ * Create a new HibernateTemplate instance.
+ * @param sessionFactory the SessionFactory to create Sessions with
+ */
+ public HibernateTemplate(SessionFactory sessionFactory) {
+ setSessionFactory(sessionFactory);
+ afterPropertiesSet();
+ }
+
+
+ /**
+ * Set the Hibernate SessionFactory that should be used to create
+ * Hibernate Sessions.
+ */
+ public void setSessionFactory(@Nullable SessionFactory sessionFactory) {
+ this.sessionFactory = sessionFactory;
+ }
+
+ /**
+ * Return the Hibernate SessionFactory that should be used to create
+ * Hibernate Sessions.
+ */
+ @Nullable
+ public SessionFactory getSessionFactory() {
+ return this.sessionFactory;
+ }
+
+ /**
+ * Obtain the SessionFactory for actual use.
+ * @return the SessionFactory (never {@code null})
+ * @throws IllegalStateException in case of no SessionFactory set
+ * @since 5.0
+ */
+ protected final SessionFactory obtainSessionFactory() {
+ SessionFactory sessionFactory = getSessionFactory();
+ Assert.state(sessionFactory != null, "No SessionFactory set");
+ return sessionFactory;
+ }
+
+ /**
+ * Set one or more names of Hibernate filters to be activated for all
+ * Sessions that this accessor works with.
+ * Each of those filters will be enabled at the beginning of each
+ * operation and correspondingly disabled at the end of the operation.
+ * This will work for newly opened Sessions as well as for existing
+ * Sessions (for example, within a transaction).
+ * @see #enableFilters(Session)
+ * @see Session#enableFilter(String)
+ */
+ public void setFilterNames(@Nullable String... filterNames) {
+ this.filterNames = filterNames;
+ }
+
+ /**
+ * Return the names of Hibernate filters to be activated, if any.
+ */
+ @Nullable
+ public String[] getFilterNames() {
+ return this.filterNames;
+ }
+
+ /**
+ * Set whether to expose the native Hibernate Session to
+ * HibernateCallback code.
+ * Default is "false": a Session proxy will be returned, suppressing
+ * {@code close} calls and automatically applying query cache
+ * settings and transaction timeouts.
+ * @see HibernateCallback
+ * @see Session
+ * @see #setCacheQueries
+ * @see #setQueryCacheRegion
+ * @see #prepareQuery
+ * @see #prepareCriteria
+ */
+ public void setExposeNativeSession(boolean exposeNativeSession) {
+ this.exposeNativeSession = exposeNativeSession;
+ }
+
+ /**
+ * Return whether to expose the native Hibernate Session to
+ * HibernateCallback code, or rather a Session proxy.
+ */
+ public boolean isExposeNativeSession() {
+ return this.exposeNativeSession;
+ }
+
+ /**
+ * Set whether to check that the Hibernate Session is not in read-only mode
+ * in case of write operations (save/update/delete).
+ * Default is "true", for fail-fast behavior when attempting write operations
+ * within a read-only transaction. Turn this off to allow save/update/delete
+ * on a Session with flush mode MANUAL.
+ * @see #checkWriteOperationAllowed
+ * @see org.springframework.transaction.TransactionDefinition#isReadOnly
+ */
+ public void setCheckWriteOperations(boolean checkWriteOperations) {
+ this.checkWriteOperations = checkWriteOperations;
+ }
+
+ /**
+ * Return whether to check that the Hibernate Session is not in read-only
+ * mode in case of write operations (save/update/delete).
+ */
+ public boolean isCheckWriteOperations() {
+ return this.checkWriteOperations;
+ }
+
+ /**
+ * Set whether to cache all queries executed by this template.
+ * If this is "true", all Query and Criteria objects created by
+ * this template will be marked as cacheable (including all
+ * queries through find methods).
+ * To specify the query region to be used for queries cached
+ * by this template, set the "queryCacheRegion" property.
+ * @see #setQueryCacheRegion
+ * @see Query#setCacheable
+ * @see Criteria#setCacheable
+ */
+ public void setCacheQueries(boolean cacheQueries) {
+ this.cacheQueries = cacheQueries;
+ }
+
+ /**
+ * Return whether to cache all queries executed by this template.
+ */
+ public boolean isCacheQueries() {
+ return this.cacheQueries;
+ }
+
+ /**
+ * Set the name of the cache region for queries executed by this template.
+ * If this is specified, it will be applied to all Query and Criteria objects
+ * created by this template (including all queries through find methods).
+ * The cache region will not take effect unless queries created by this
+ * template are configured to be cached via the "cacheQueries" property.
+ * @see #setCacheQueries
+ * @see Query#setCacheRegion
+ * @see Criteria#setCacheRegion
+ */
+ public void setQueryCacheRegion(@Nullable String queryCacheRegion) {
+ this.queryCacheRegion = queryCacheRegion;
+ }
+
+ /**
+ * Return the name of the cache region for queries executed by this template.
+ */
+ @Nullable
+ public String getQueryCacheRegion() {
+ return this.queryCacheRegion;
+ }
+
+ /**
+ * Set the fetch size for this HibernateTemplate. This is important for processing
+ * large result sets: Setting this higher than the default value will increase
+ * processing speed at the cost of memory consumption; setting this lower can
+ * avoid transferring row data that will never be read by the application.
+ * Default is 0, indicating to use the JDBC driver's default.
+ */
+ public void setFetchSize(int fetchSize) {
+ this.fetchSize = fetchSize;
+ }
+
+ /**
+ * Return the fetch size specified for this HibernateTemplate.
+ */
+ public int getFetchSize() {
+ return this.fetchSize;
+ }
+
+ /**
+ * Set the maximum number of rows for this HibernateTemplate. This is important
+ * for processing subsets of large result sets, avoiding to read and hold
+ * the entire result set in the database or in the JDBC driver if we're
+ * never interested in the entire result in the first place (for example,
+ * when performing searches that might return a large number of matches).
+ * Default is 0, indicating to use the JDBC driver's default.
+ */
+ public void setMaxResults(int maxResults) {
+ this.maxResults = maxResults;
+ }
+
+ /**
+ * Return the maximum number of rows specified for this HibernateTemplate.
+ */
+ public int getMaxResults() {
+ return this.maxResults;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ if (getSessionFactory() == null) {
+ throw new IllegalArgumentException("Property 'sessionFactory' is required");
+ }
+ }
+
+
+ @Override
+ @Nullable
+ public This execute variant overrides the template-wide
+ * {@link #isExposeNativeSession() "exposeNativeSession"} setting.
+ * @param action callback object that specifies the Hibernate action
+ * @return a result object returned by the action, or {@code null}
+ * @throws DataAccessException in case of Hibernate errors
+ */
+ @Nullable
+ public Default implementation throws an InvalidDataAccessApiUsageException in
+ * case of {@code FlushMode.MANUAL}. Can be overridden in subclasses.
+ * @param session current Hibernate Session
+ * @throws InvalidDataAccessApiUsageException if write operations are not allowed
+ * @see #setCheckWriteOperations
+ * @see Session#getFlushMode()
+ * @see FlushMode#MANUAL
+ */
+ protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
+ if (isCheckWriteOperations() && session.getHibernateFlushMode().lessThan(FlushMode.COMMIT)) {
+ throw new InvalidDataAccessApiUsageException(
+ "Write operations are not allowed in read-only mode (FlushMode.MANUAL): "+
+ "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
+ }
+ }
+
+ /**
+ * Prepare the given Criteria object, applying cache settings and/or
+ * a transaction timeout.
+ * @param criteria the Criteria object to prepare
+ * @see #setCacheQueries
+ * @see #setQueryCacheRegion
+ */
+ protected void prepareCriteria(Criteria criteria) {
+ if (isCacheQueries()) {
+ criteria.setCacheable(true);
+ if (getQueryCacheRegion() != null) {
+ criteria.setCacheRegion(getQueryCacheRegion());
+ }
+ }
+ if (getFetchSize() > 0) {
+ criteria.setFetchSize(getFetchSize());
+ }
+ if (getMaxResults() > 0) {
+ criteria.setMaxResults(getMaxResults());
+ }
+
+ ResourceHolderSupport sessionHolder =
+ (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory());
+ if (sessionHolder != null && sessionHolder.hasTimeout()) {
+ criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds());
+ }
+ }
+
+ /**
+ * Prepare the given Query object, applying cache settings and/or
+ * a transaction timeout.
+ * @param queryObject the Query object to prepare
+ * @see #setCacheQueries
+ * @see #setQueryCacheRegion
+ */
+ protected void prepareQuery(Query> queryObject) {
+ if (isCacheQueries()) {
+ queryObject.setCacheable(true);
+ if (getQueryCacheRegion() != null) {
+ queryObject.setCacheRegion(getQueryCacheRegion());
+ }
+ }
+ if (getFetchSize() > 0) {
+ queryObject.setFetchSize(getFetchSize());
+ }
+ if (getMaxResults() > 0) {
+ queryObject.setMaxResults(getMaxResults());
+ }
+
+ ResourceHolderSupport sessionHolder =
+ (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory());
+ if (sessionHolder != null && sessionHolder.hasTimeout()) {
+ queryObject.setTimeout(sessionHolder.getTimeToLiveInSeconds());
+ }
+ }
+
+ /**
+ * Apply the given name parameter to the given Query object.
+ * @param queryObject the Query object
+ * @param paramName the name of the parameter
+ * @param value the value of the parameter
+ * @throws HibernateException if thrown by the Query object
+ */
+ protected void applyNamedParameterToQuery(Query> queryObject, String paramName, Object value)
+ throws HibernateException {
+
+ if (value instanceof Collection> collection) {
+ queryObject.setParameterList(paramName, collection);
+ }
+ else if (value instanceof Object[] array) {
+ queryObject.setParameterList(paramName, array);
+ }
+ else {
+ queryObject.setParameter(paramName, value);
+ }
+ }
+
+ private static Supports custom isolation levels, and timeouts that get applied as
+ * Hibernate transaction timeouts.
+ *
+ * This transaction manager is appropriate for applications that use a single
+ * Hibernate SessionFactory for transactional data access, but it also supports
+ * direct DataSource access within a transaction (i.e. plain JDBC code working
+ * with the same DataSource). This allows for mixing services which access Hibernate
+ * and services which use plain JDBC (without being aware of Hibernate)!
+ * Application code needs to stick to the same simple Connection lookup pattern as
+ * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
+ * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
+ * or going through a
+ * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
+ *
+ * Note: To be able to register a DataSource's Connection for plain JDBC code,
+ * this instance needs to be aware of the DataSource ({@link #setDataSource}).
+ * The given DataSource should obviously match the one used by the given SessionFactory.
+ *
+ * JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager})
+ * is necessary for accessing multiple transactional resources within the same
+ * transaction. The DataSource that Hibernate uses needs to be JTA-enabled in
+ * such a scenario (see container setup).
+ *
+ * This transaction manager supports nested transactions via JDBC Savepoints.
+ * The {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults
+ * to "false", though, as nested transactions will just apply to the JDBC Connection,
+ * not to the Hibernate Session and its cached entity objects and related context.
+ * You can manually set the flag to "true" if you want to use nested transactions
+ * for JDBC access code which participates in Hibernate transactions (provided that
+ * your JDBC driver supports savepoints). Note that Hibernate itself does not
+ * support nested transactions! Hence, do not expect Hibernate access code to
+ * semantically participate in a nested transaction.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see #setSessionFactory
+ * @see SessionFactory#getCurrentSession()
+ * @see org.springframework.jdbc.core.JdbcTemplate
+ * @see org.springframework.jdbc.support.JdbcTransactionManager
+ * @see org.springframework.orm.jpa.JpaTransactionManager
+ * @see org.springframework.orm.jpa.vendor.HibernateJpaDialect
+ */
+@SuppressWarnings("serial")
+public class HibernateTransactionManager extends AbstractPlatformTransactionManager
+ implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
+
+ @Nullable
+ private SessionFactory sessionFactory;
+
+ @Nullable
+ private DataSource dataSource;
+
+ private boolean autodetectDataSource = true;
+
+ private boolean prepareConnection = true;
+
+ private boolean allowResultAccessAfterCompletion = false;
+
+ private boolean hibernateManagedSession = false;
+
+ @Nullable
+ private Consumer The DataSource should match the one used by the Hibernate SessionFactory:
+ * for example, you could specify the same JNDI DataSource for both.
+ * If the SessionFactory was configured with LocalDataSourceConnectionProvider,
+ * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource",
+ * the DataSource will be auto-detected. You can still explicitly specify the
+ * DataSource, but you don't need to in this case.
+ * A transactional JDBC Connection for this DataSource will be provided to
+ * application code accessing this DataSource directly via DataSourceUtils
+ * or JdbcTemplate. The Connection will be taken from the Hibernate Session.
+ * The DataSource specified here should be the target DataSource to manage
+ * transactions for, not a TransactionAwareDataSourceProxy. Only data access
+ * code may work with TransactionAwareDataSourceProxy, while the transaction
+ * manager needs to work on the underlying target DataSource. If there's
+ * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
+ * unwrapped to extract its target DataSource.
+ * NOTE: For scenarios with many transactions that just read data from
+ * Hibernate's cache (and do not actually access the database), consider using
+ * a {@link org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy}
+ * for the actual target DataSource. Alternatively, consider switching
+ * {@link #setPrepareConnection "prepareConnection"} to {@code false}.
+ * In both cases, this transaction manager will not eagerly acquire a
+ * JDBC Connection for each Hibernate Session.
+ * @see #setAutodetectDataSource
+ * @see TransactionAwareDataSourceProxy
+ * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy
+ * @see org.springframework.jdbc.core.JdbcTemplate
+ */
+ public void setDataSource(@Nullable DataSource dataSource) {
+ if (dataSource instanceof TransactionAwareDataSourceProxy proxy) {
+ // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
+ // for its underlying target DataSource, else data access code won't see
+ // properly exposed transactions (i.e. transactions for the target DataSource).
+ this.dataSource = proxy.getTargetDataSource();
+ }
+ else {
+ this.dataSource = dataSource;
+ }
+ }
+
+ /**
+ * Return the JDBC DataSource that this instance manages transactions for.
+ */
+ @Nullable
+ public DataSource getDataSource() {
+ return this.dataSource;
+ }
+
+ /**
+ * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory,
+ * if set via LocalSessionFactoryBean's {@code setDataSource}. Default is "true".
+ * Can be turned off to deliberately ignore an available DataSource, in order
+ * to not expose Hibernate transactions as JDBC transactions for that DataSource.
+ * @see #setDataSource
+ */
+ public void setAutodetectDataSource(boolean autodetectDataSource) {
+ this.autodetectDataSource = autodetectDataSource;
+ }
+
+ /**
+ * Set whether to prepare the underlying JDBC Connection of a transactional
+ * Hibernate Session, that is, whether to apply a transaction-specific
+ * isolation level and/or the transaction's read-only flag to the underlying
+ * JDBC Connection.
+ * Default is "true". If you turn this flag off, the transaction manager
+ * will not support per-transaction isolation levels anymore. It will not
+ * call {@code Connection.setReadOnly(true)} for read-only transactions
+ * anymore either. If this flag is turned off, no cleanup of a JDBC Connection
+ * is required after a transaction, since no Connection settings will get modified.
+ * @see Connection#setTransactionIsolation
+ * @see Connection#setReadOnly
+ */
+ public void setPrepareConnection(boolean prepareConnection) {
+ this.prepareConnection = prepareConnection;
+ }
+
+ /**
+ * Set whether to allow result access after completion, typically via Hibernate's
+ * ScrollableResults mechanism.
+ * Default is "false". Turning this flag on enforces over-commit holdability on the
+ * underlying JDBC Connection (if {@link #prepareConnection "prepareConnection"} is on)
+ * and skips the disconnect-on-completion step.
+ * @see Connection#setHoldability
+ * @see ResultSet#HOLD_CURSORS_OVER_COMMIT
+ * @see #disconnectOnCompletion(Session)
+ * @deprecated as of 5.3.29 since Hibernate 5.x aggressively closes ResultSets on commit,
+ * making it impossible to rely on ResultSet holdability. Also, Spring does not provide
+ * an equivalent setting on {@link org.springframework.orm.jpa.JpaTransactionManager}.
+ */
+ @Deprecated(since = "5.3.29")
+ public void setAllowResultAccessAfterCompletion(boolean allowResultAccessAfterCompletion) {
+ this.allowResultAccessAfterCompletion = allowResultAccessAfterCompletion;
+ }
+
+ /**
+ * Set whether to operate on a Hibernate-managed Session instead of a
+ * Spring-managed Session, that is, whether to obtain the Session through
+ * Hibernate's {@link SessionFactory#getCurrentSession()} instead of
+ * {@link SessionFactory#openSession()} (with a Spring
+ * {@link TransactionSynchronizationManager} check preceding it).
+ * Default is "false", i.e. using a Spring-managed Session: taking the current
+ * thread-bound Session if available (for example, in an Open-Session-in-View scenario),
+ * creating a new Session for the current transaction otherwise.
+ * Switch this flag to "true" in order to enforce use of a Hibernate-managed Session.
+ * Note that this requires {@link SessionFactory#getCurrentSession()}
+ * to always return a proper Session when called for a Spring-managed transaction;
+ * transaction begin will fail if the {@code getCurrentSession()} call fails.
+ * This mode will typically be used in combination with a custom Hibernate
+ * {@link org.hibernate.context.spi.CurrentSessionContext} implementation that stores
+ * Sessions in a place other than Spring's TransactionSynchronizationManager.
+ * It may also be used in combination with Spring's Open-Session-in-View support
+ * (using Spring's default {@link SpringSessionContext}), in which case it subtly
+ * differs from the Spring-managed Session mode: The pre-bound Session will not
+ * receive a {@code clear()} call (on rollback) or a {@code disconnect()}
+ * call (on transaction completion) in such a scenario; this is rather left up
+ * to a custom CurrentSessionContext implementation (if desired).
+ */
+ public void setHibernateManagedSession(boolean hibernateManagedSession) {
+ this.hibernateManagedSession = hibernateManagedSession;
+ }
+
+ /**
+ * Specify a callback for customizing every Hibernate {@code Session} resource
+ * created for a new transaction managed by this {@code HibernateTransactionManager}.
+ * This enables convenient customizations for application purposes, for example,
+ * setting Hibernate filters.
+ * @since 5.3
+ * @see Session#enableFilter
+ */
+ public void setSessionInitializer(Consumer Requires the bean factory to be known, to be able to resolve the bean
+ * name to an interceptor instance on session creation. Typically used for
+ * prototype interceptors, i.e. a new interceptor instance per session.
+ * Can also be used for shared interceptor instances, but it is recommended
+ * to set the interceptor reference directly in such a scenario.
+ * @param entityInterceptorBeanName the name of the entity interceptor in
+ * the bean factory
+ * @see #setBeanFactory
+ * @see #setEntityInterceptor
+ */
+ public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
+ this.entityInterceptor = entityInterceptorBeanName;
+ }
+
+ /**
+ * Set a Hibernate entity interceptor that allows to inspect and change
+ * property values before writing to and reading from the database.
+ * Will get applied to any new Session created by this transaction manager.
+ * Such an interceptor can either be set at the SessionFactory level,
+ * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
+ * HibernateTransactionManager.
+ * @see LocalSessionFactoryBean#setEntityInterceptor
+ */
+ public void setEntityInterceptor(@Nullable Interceptor entityInterceptor) {
+ this.entityInterceptor = entityInterceptor;
+ }
+
+ /**
+ * Return the current Hibernate entity interceptor, or {@code null} if none.
+ * Resolves an entity interceptor bean name via the bean factory,
+ * if necessary.
+ * @throws IllegalStateException if bean name specified but no bean factory set
+ * @throws BeansException if bean name resolution via the bean factory failed
+ * @see #setEntityInterceptor
+ * @see #setEntityInterceptorBeanName
+ * @see #setBeanFactory
+ */
+ @Nullable
+ public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
+ if (this.entityInterceptor instanceof Interceptor interceptor) {
+ return interceptor;
+ }
+ else if (this.entityInterceptor instanceof String beanName) {
+ if (this.beanFactory == null) {
+ throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
+ }
+ return this.beanFactory.getBean(beanName, Interceptor.class);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * The bean factory just needs to be known for resolving entity interceptor
+ * bean names. It does not need to be set for any other mode of operation.
+ * @see #setEntityInterceptorBeanName
+ */
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ if (getSessionFactory() == null) {
+ throw new IllegalArgumentException("Property 'sessionFactory' is required");
+ }
+ if (this.entityInterceptor instanceof String && this.beanFactory == null) {
+ throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'");
+ }
+
+ // Check for SessionFactory's DataSource.
+ if (this.autodetectDataSource && getDataSource() == null) {
+ DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory());
+ if (sfds != null) {
+ // Use the SessionFactory's DataSource for exposing transactions to JDBC code.
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using DataSource [" + sfds +
+ "] of Hibernate SessionFactory for HibernateTransactionManager");
+ }
+ setDataSource(sfds);
+ }
+ }
+ }
+
+
+ @Override
+ public Object getResourceFactory() {
+ return obtainSessionFactory();
+ }
+
+ @Override
+ protected Object doGetTransaction() {
+ HibernateTransactionObject txObject = new HibernateTransactionObject();
+ txObject.setSavepointAllowed(isNestedTransactionAllowed());
+
+ SessionFactory sessionFactory = obtainSessionFactory();
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
+ if (sessionHolder != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
+ }
+ txObject.setSessionHolder(sessionHolder);
+ }
+ else if (this.hibernateManagedSession) {
+ try {
+ Session session = sessionFactory.getCurrentSession();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
+ }
+ txObject.setExistingSession(session);
+ }
+ catch (HibernateException ex) {
+ throw new DataAccessResourceFailureException(
+ "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
+ }
+ }
+
+ if (getDataSource() != null) {
+ ConnectionHolder conHolder = (ConnectionHolder)
+ TransactionSynchronizationManager.getResource(getDataSource());
+ txObject.setConnectionHolder(conHolder);
+ }
+
+ return txObject;
+ }
+
+ @Override
+ protected boolean isExistingTransaction(Object transaction) {
+ HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
+ return (txObject.hasSpringManagedTransaction() ||
+ (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction()));
+ }
+
+ @Override
+ protected void doBegin(Object transaction, TransactionDefinition definition) {
+ HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
+
+ if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
+ throw new IllegalTransactionStateException(
+ "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
+ "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
+ "It is recommended to use a single HibernateTransactionManager for all transactions " +
+ "on a single DataSource, no matter whether Hibernate or JDBC access.");
+ }
+
+ SessionImplementor session = null;
+
+ try {
+ if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
+ Interceptor entityInterceptor = getEntityInterceptor();
+ Session newSession = (entityInterceptor != null ?
+ obtainSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
+ obtainSessionFactory().openSession());
+ if (this.sessionInitializer != null) {
+ this.sessionInitializer.accept(newSession);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
+ }
+ txObject.setSession(newSession);
+ }
+
+ session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor.class);
+
+ boolean holdabilityNeeded = (this.allowResultAccessAfterCompletion && !txObject.isNewSession());
+ boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
+ if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
+ if (this.prepareConnection && ConnectionReleaseMode.ON_CLOSE.equals(
+ session.getJdbcCoordinator().getLogicalConnection().getConnectionHandlingMode().getReleaseMode())) {
+ // We're allowed to change the transaction settings of the JDBC Connection.
+ if (logger.isDebugEnabled()) {
+ logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
+ }
+ Connection con = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection();
+ Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
+ txObject.setPreviousIsolationLevel(previousIsolationLevel);
+ txObject.setReadOnly(definition.isReadOnly());
+ if (holdabilityNeeded) {
+ int currentHoldability = con.getHoldability();
+ if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+ txObject.setPreviousHoldability(currentHoldability);
+ con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
+ }
+ }
+ txObject.connectionPrepared();
+ }
+ else {
+ // Not allowed to change the transaction settings of the JDBC Connection.
+ if (isolationLevelNeeded) {
+ // We should set a specific isolation level but are not allowed to...
+ throw new InvalidIsolationLevelException(
+ "HibernateTransactionManager is not allowed to support custom isolation levels: " +
+ "make sure that its 'prepareConnection' flag is on (the default) and that the " +
+ "Hibernate connection release mode is set to ON_CLOSE.");
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
+ }
+ }
+ }
+
+ if (definition.isReadOnly() && txObject.isNewSession()) {
+ // Just set to MANUAL in case of a new Session for this transaction.
+ session.setHibernateFlushMode(FlushMode.MANUAL);
+ // As of 5.1, we're also setting Hibernate's read-only entity mode by default.
+ session.setDefaultReadOnly(true);
+ }
+
+ if (!definition.isReadOnly() && !txObject.isNewSession()) {
+ // We need AUTO or COMMIT for a non-read-only transaction.
+ FlushMode flushMode = session.getHibernateFlushMode();
+ if (FlushMode.MANUAL.equals(flushMode)) {
+ session.setHibernateFlushMode(FlushMode.AUTO);
+ txObject.getSessionHolder().setPreviousFlushMode(flushMode);
+ }
+ }
+
+ Transaction hibTx;
+
+ // Register transaction timeout.
+ int timeout = determineTimeout(definition);
+ if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
+ // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
+ // Applies to all statements, also to inserts, updates and deletes!
+ hibTx = session.getTransaction();
+ hibTx.setTimeout(timeout);
+ hibTx.begin();
+ }
+ else {
+ // Open a plain Hibernate transaction without specified timeout.
+ hibTx = session.beginTransaction();
+ }
+
+ // Add the Hibernate transaction to the session holder.
+ txObject.getSessionHolder().setTransaction(hibTx);
+
+ // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
+ if (getDataSource() != null) {
+ final SessionImplementor sessionToUse = session;
+ ConnectionHolder conHolder = new ConnectionHolder(
+ () -> sessionToUse.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection());
+ if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
+ conHolder.setTimeoutInSeconds(timeout);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exposing Hibernate transaction as JDBC [" + conHolder.getConnectionHandle() + "]");
+ }
+ TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
+ txObject.setConnectionHolder(conHolder);
+ }
+
+ // Bind the session holder to the thread.
+ if (txObject.isNewSessionHolder()) {
+ TransactionSynchronizationManager.bindResource(obtainSessionFactory(), txObject.getSessionHolder());
+ }
+ txObject.getSessionHolder().setSynchronizedWithTransaction(true);
+ }
+
+ catch (Throwable ex) {
+ if (txObject.isNewSession()) {
+ try {
+ if (session != null && session.getTransaction().getStatus() == TransactionStatus.ACTIVE) {
+ session.getTransaction().rollback();
+ }
+ }
+ catch (Throwable ex2) {
+ logger.debug("Could not rollback Session after failed transaction begin", ex);
+ }
+ finally {
+ SessionFactoryUtils.closeSession(session);
+ txObject.setSessionHolder(null);
+ }
+ }
+ throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
+ }
+ }
+
+ @Override
+ protected Object doSuspend(Object transaction) {
+ HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
+ txObject.setSessionHolder(null);
+ SessionHolder sessionHolder =
+ (SessionHolder) TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
+ txObject.setConnectionHolder(null);
+ ConnectionHolder connectionHolder = null;
+ if (getDataSource() != null) {
+ connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
+ }
+ return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
+ }
+
+ @Override
+ protected void doResume(@Nullable Object transaction, Object suspendedResources) {
+ SessionFactory sessionFactory = obtainSessionFactory();
+
+ SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
+ if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
+ // From non-transactional code running in active transaction synchronization
+ // -> can be safely removed, will be closed on transaction completion.
+ TransactionSynchronizationManager.unbindResource(sessionFactory);
+ }
+ TransactionSynchronizationManager.bindResource(sessionFactory, resourcesHolder.getSessionHolder());
+ ConnectionHolder connectionHolder = resourcesHolder.getConnectionHolder();
+ if (connectionHolder != null && getDataSource() != null) {
+ TransactionSynchronizationManager.bindResource(getDataSource(), connectionHolder);
+ }
+ }
+
+ @Override
+ protected void doCommit(DefaultTransactionStatus status) {
+ HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
+ Transaction hibTx = txObject.getSessionHolder().getTransaction();
+ Assert.state(hibTx != null, "No Hibernate transaction");
+ if (status.isDebug()) {
+ logger.debug("Committing Hibernate transaction on Session [" +
+ txObject.getSessionHolder().getSession() + "]");
+ }
+
+ try {
+ hibTx.commit();
+ }
+ catch (org.hibernate.TransactionException ex) {
+ // assumably from commit call to the underlying JDBC connection
+ throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
+ }
+ catch (HibernateException ex) {
+ // assumably failed to flush changes to database
+ throw convertHibernateAccessException(ex);
+ }
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException hibernateEx) {
+ throw convertHibernateAccessException(hibernateEx);
+ }
+ throw ex;
+ }
+ }
+
+ @Override
+ protected void doRollback(DefaultTransactionStatus status) {
+ HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
+ Transaction hibTx = txObject.getSessionHolder().getTransaction();
+ Assert.state(hibTx != null, "No Hibernate transaction");
+ if (status.isDebug()) {
+ logger.debug("Rolling back Hibernate transaction on Session [" +
+ txObject.getSessionHolder().getSession() + "]");
+ }
+
+ try {
+ hibTx.rollback();
+ }
+ catch (org.hibernate.TransactionException ex) {
+ throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
+ }
+ catch (HibernateException ex) {
+ // Shouldn't really happen, as a rollback doesn't cause a flush.
+ throw convertHibernateAccessException(ex);
+ }
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException hibernateEx) {
+ throw convertHibernateAccessException(hibernateEx);
+ }
+ throw ex;
+ }
+ finally {
+ if (!txObject.isNewSession() && !this.hibernateManagedSession) {
+ // Clear all pending inserts/updates/deletes in the Session.
+ // Necessary for pre-bound Sessions, to avoid inconsistent state.
+ txObject.getSessionHolder().getSession().clear();
+ }
+ }
+ }
+
+ @Override
+ protected void doSetRollbackOnly(DefaultTransactionStatus status) {
+ HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
+ if (status.isDebug()) {
+ logger.debug("Setting Hibernate transaction on Session [" +
+ txObject.getSessionHolder().getSession() + "] rollback-only");
+ }
+ txObject.setRollbackOnly();
+ }
+
+ @Override
+ protected void doCleanupAfterCompletion(Object transaction) {
+ HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
+
+ // Remove the session holder from the thread.
+ if (txObject.isNewSessionHolder()) {
+ TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
+ }
+
+ // Remove the JDBC connection holder from the thread, if exposed.
+ if (getDataSource() != null) {
+ TransactionSynchronizationManager.unbindResource(getDataSource());
+ }
+
+ SessionImplementor session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor.class);
+ if (txObject.needsConnectionReset() &&
+ session.getJdbcCoordinator().getLogicalConnection().isPhysicallyConnected()) {
+ // We're running with connection release mode ON_CLOSE: We're able to reset
+ // the isolation level and/or read-only flag of the JDBC Connection here.
+ // Else, we need to rely on the connection pool to perform proper cleanup.
+ try {
+ Connection con = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection();
+ Integer previousHoldability = txObject.getPreviousHoldability();
+ if (previousHoldability != null) {
+ con.setHoldability(previousHoldability);
+ }
+ DataSourceUtils.resetConnectionAfterTransaction(
+ con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
+ }
+ catch (HibernateException ex) {
+ logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
+ }
+ catch (Throwable ex) {
+ logger.debug("Could not reset JDBC Connection after transaction", ex);
+ }
+ }
+
+ if (txObject.isNewSession()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Closing Hibernate Session [" + session + "] after transaction");
+ }
+ SessionFactoryUtils.closeSession(session);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction");
+ }
+ if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
+ session.setHibernateFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
+ }
+ if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
+ disconnectOnCompletion(session);
+ }
+ }
+ txObject.getSessionHolder().clear();
+ }
+
+ /**
+ * Disconnect a pre-existing Hibernate Session on transaction completion,
+ * returning its database connection but preserving its entity state.
+ * The default implementation calls the equivalent of {@link Session#disconnect()}.
+ * Subclasses may override this with a no-op or with fine-tuned disconnection logic.
+ * @param session the Hibernate Session to disconnect
+ * @see Session#disconnect()
+ */
+ protected void disconnectOnCompletion(Session session) {
+ if (session instanceof SessionImplementor sessionImpl) {
+ sessionImpl.getJdbcCoordinator().getLogicalConnection().manualDisconnect();
+ }
+ }
+
+ /**
+ * Convert the given HibernateException to an appropriate exception
+ * from the {@code org.springframework.dao} hierarchy.
+ * @param ex the HibernateException that occurred
+ * @return a corresponding DataAccessException
+ * @see SessionFactoryUtils#convertHibernateAccessException
+ */
+ protected DataAccessException convertHibernateAccessException(HibernateException ex) {
+ return SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+
+
+ /**
+ * Hibernate transaction object, representing a SessionHolder.
+ * Used as transaction object by HibernateTransactionManager.
+ */
+ private class HibernateTransactionObject extends JdbcTransactionObjectSupport {
+
+ @Nullable
+ private SessionHolder sessionHolder;
+
+ private boolean newSessionHolder;
+
+ private boolean newSession;
+
+ private boolean needsConnectionReset;
+
+ @Nullable
+ private Integer previousHoldability;
+
+ public void setSession(Session session) {
+ this.sessionHolder = new SessionHolder(session);
+ this.newSessionHolder = true;
+ this.newSession = true;
+ }
+
+ public void setExistingSession(Session session) {
+ this.sessionHolder = new SessionHolder(session);
+ this.newSessionHolder = true;
+ this.newSession = false;
+ }
+
+ public void setSessionHolder(@Nullable SessionHolder sessionHolder) {
+ this.sessionHolder = sessionHolder;
+ this.newSessionHolder = false;
+ this.newSession = false;
+ }
+
+ public SessionHolder getSessionHolder() {
+ Assert.state(this.sessionHolder != null, "No SessionHolder available");
+ return this.sessionHolder;
+ }
+
+ public boolean hasSessionHolder() {
+ return (this.sessionHolder != null);
+ }
+
+ public boolean isNewSessionHolder() {
+ return this.newSessionHolder;
+ }
+
+ public boolean isNewSession() {
+ return this.newSession;
+ }
+
+ public void connectionPrepared() {
+ this.needsConnectionReset = true;
+ }
+
+ public boolean needsConnectionReset() {
+ return this.needsConnectionReset;
+ }
+
+ public void setPreviousHoldability(@Nullable Integer previousHoldability) {
+ this.previousHoldability = previousHoldability;
+ }
+
+ @Nullable
+ public Integer getPreviousHoldability() {
+ return this.previousHoldability;
+ }
+
+ public boolean hasSpringManagedTransaction() {
+ return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
+ }
+
+ public boolean hasHibernateManagedTransaction() {
+ return (this.sessionHolder != null &&
+ this.sessionHolder.getSession().getTransaction().getStatus() == TransactionStatus.ACTIVE);
+ }
+
+ public void setRollbackOnly() {
+ getSessionHolder().setRollbackOnly();
+ if (hasConnectionHolder()) {
+ getConnectionHolder().setRollbackOnly();
+ }
+ }
+
+ @Override
+ public boolean isRollbackOnly() {
+ return getSessionHolder().isRollbackOnly() ||
+ (hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
+ }
+
+ @Override
+ public void flush() {
+ try {
+ getSessionHolder().getSession().flush();
+ }
+ catch (HibernateException ex) {
+ throw convertHibernateAccessException(ex);
+ }
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException hibernateEx) {
+ throw convertHibernateAccessException(hibernateEx);
+ }
+ throw ex;
+ }
+ }
+ }
+
+
+ /**
+ * Holder for suspended resources.
+ * Used internally by {@code doSuspend} and {@code doResume}.
+ */
+ private static final class SuspendedResourcesHolder {
+
+ private final SessionHolder sessionHolder;
+
+ @Nullable
+ private final ConnectionHolder connectionHolder;
+
+ private SuspendedResourcesHolder(SessionHolder sessionHolder, @Nullable ConnectionHolder conHolder) {
+ this.sessionHolder = sessionHolder;
+ this.connectionHolder = conHolder;
+ }
+
+ private SessionHolder getSessionHolder() {
+ return this.sessionHolder;
+ }
+
+ @Nullable
+ private ConnectionHolder getConnectionHolder() {
+ return this.connectionHolder;
+ }
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/LocalSessionFactoryBean.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/LocalSessionFactoryBean.java
new file mode 100644
index 00000000000..5bae09ba43a
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/LocalSessionFactoryBean.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.hibernate.Interceptor;
+import org.hibernate.SessionFactory;
+import org.hibernate.boot.MetadataSources;
+import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
+import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
+import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
+import org.hibernate.cache.spi.RegionFactory;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
+import org.hibernate.integrator.spi.Integrator;
+import org.hibernate.service.ServiceRegistry;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.InfrastructureProxy;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.lang.Nullable;
+
+/**
+ * {@link FactoryBean} that creates a Hibernate {@link SessionFactory}. This is the usual
+ * way to set up a shared Hibernate SessionFactory in a Spring application context; the
+ * SessionFactory can then be passed to data access objects via dependency injection.
+ *
+ * Compatible with Hibernate ORM 5.5/5.6, as of Spring Framework 6.0.
+ * This Hibernate-specific {@code LocalSessionFactoryBean} can be an immediate alternative
+ * to {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean} for
+ * common JPA purposes: The Hibernate {@code SessionFactory} will natively expose the JPA
+ * {@code EntityManagerFactory} interface as well, and Hibernate {@code BeanContainer}
+ * integration will be registered out of the box. In combination with
+ * {@link HibernateTransactionManager}, this naturally allows for mixing JPA access code
+ * with native Hibernate access code within the same transaction.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see #setDataSource
+ * @see #setPackagesToScan
+ * @see HibernateTransactionManager
+ * @see LocalSessionFactoryBuilder
+ * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
+ */
+public class LocalSessionFactoryBean extends HibernateExceptionTranslator
+ implements FactoryBean If this is set, the Hibernate settings should not define
+ * a connection provider to avoid meaningless double configuration.
+ */
+ public void setDataSource(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ /**
+ * Set the location of a single Hibernate XML config file, for example as
+ * classpath resource "classpath:hibernate.cfg.xml".
+ * Note: Can be omitted when all necessary properties and mapping
+ * resources are specified locally via this bean.
+ * @see Configuration#configure(java.net.URL)
+ */
+ public void setConfigLocation(Resource configLocation) {
+ this.configLocations = new Resource[] {configLocation};
+ }
+
+ /**
+ * Set the locations of multiple Hibernate XML config files, for example as
+ * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml".
+ * Note: Can be omitted when all necessary properties and mapping
+ * resources are specified locally via this bean.
+ * @see Configuration#configure(java.net.URL)
+ */
+ public void setConfigLocations(Resource... configLocations) {
+ this.configLocations = configLocations;
+ }
+
+ /**
+ * Set Hibernate mapping resources to be found in the class path,
+ * like "example.hbm.xml" or "mypackage/example.hbm.xml".
+ * Analogous to mapping entries in a Hibernate XML config file.
+ * Alternative to the more generic setMappingLocations method.
+ * Can be used to add to mappings from a Hibernate XML config file,
+ * or to specify all mappings locally.
+ * @see #setMappingLocations
+ * @see Configuration#addResource
+ */
+ public void setMappingResources(String... mappingResources) {
+ this.mappingResources = mappingResources;
+ }
+
+ /**
+ * Set locations of Hibernate mapping files, for example as classpath
+ * resource "classpath:example.hbm.xml". Supports any resource location
+ * via Spring's resource abstraction, for example relative paths like
+ * "WEB-INF/mappings/example.hbm.xml" when running in an application context.
+ * Can be used to add to mappings from a Hibernate XML config file,
+ * or to specify all mappings locally.
+ * @see Configuration#addInputStream
+ */
+ public void setMappingLocations(Resource... mappingLocations) {
+ this.mappingLocations = mappingLocations;
+ }
+
+ /**
+ * Set locations of cacheable Hibernate mapping files, for example as web app
+ * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location
+ * via Spring's resource abstraction, as long as the resource can be resolved
+ * in the file system.
+ * Can be used to add to mappings from a Hibernate XML config file,
+ * or to specify all mappings locally.
+ * @see Configuration#addCacheableFile(File)
+ */
+ public void setCacheableMappingLocations(Resource... cacheableMappingLocations) {
+ this.cacheableMappingLocations = cacheableMappingLocations;
+ }
+
+ /**
+ * Set locations of jar files that contain Hibernate mapping resources,
+ * like "WEB-INF/lib/example.hbm.jar".
+ * Can be used to add to mappings from a Hibernate XML config file,
+ * or to specify all mappings locally.
+ * @see Configuration#addJar(File)
+ */
+ public void setMappingJarLocations(Resource... mappingJarLocations) {
+ this.mappingJarLocations = mappingJarLocations;
+ }
+
+ /**
+ * Set locations of directories that contain Hibernate mapping resources,
+ * like "WEB-INF/mappings".
+ * Can be used to add to mappings from a Hibernate XML config file,
+ * or to specify all mappings locally.
+ * @see Configuration#addDirectory(File)
+ */
+ public void setMappingDirectoryLocations(Resource... mappingDirectoryLocations) {
+ this.mappingDirectoryLocations = mappingDirectoryLocations;
+ }
+
+ /**
+ * Set a Hibernate entity interceptor that allows to inspect and change
+ * property values before writing to and reading from the database.
+ * Will get applied to any new Session created by this factory.
+ * @see Configuration#setInterceptor
+ */
+ public void setEntityInterceptor(Interceptor entityInterceptor) {
+ this.entityInterceptor = entityInterceptor;
+ }
+
+ /**
+ * Set a Hibernate 5 {@link ImplicitNamingStrategy} for the SessionFactory.
+ * @see Configuration#setImplicitNamingStrategy
+ */
+ public void setImplicitNamingStrategy(ImplicitNamingStrategy implicitNamingStrategy) {
+ this.implicitNamingStrategy = implicitNamingStrategy;
+ }
+
+ /**
+ * Set a Hibernate 5 {@link PhysicalNamingStrategy} for the SessionFactory.
+ * @see Configuration#setPhysicalNamingStrategy
+ */
+ public void setPhysicalNamingStrategy(PhysicalNamingStrategy physicalNamingStrategy) {
+ this.physicalNamingStrategy = physicalNamingStrategy;
+ }
+
+ /**
+ * Set the Spring {@link org.springframework.transaction.jta.JtaTransactionManager}
+ * or the JTA {@link jakarta.transaction.TransactionManager} to be used with Hibernate,
+ * if any. Implicitly sets up {@code JtaPlatform}.
+ * @see LocalSessionFactoryBuilder#setJtaTransactionManager
+ */
+ public void setJtaTransactionManager(Object jtaTransactionManager) {
+ this.jtaTransactionManager = jtaTransactionManager;
+ }
+
+ /**
+ * Set the Hibernate {@link RegionFactory} to use for the SessionFactory.
+ * Allows for using a Spring-managed {@code RegionFactory} instance.
+ * Note: If this is set, the Hibernate settings should not define a
+ * cache provider to avoid meaningless double configuration.
+ * @since 5.1
+ * @see LocalSessionFactoryBuilder#setCacheRegionFactory
+ */
+ public void setCacheRegionFactory(RegionFactory cacheRegionFactory) {
+ this.cacheRegionFactory = cacheRegionFactory;
+ }
+
+ /**
+ * Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory.
+ * @since 4.3
+ * @see LocalSessionFactoryBuilder#setMultiTenantConnectionProvider
+ */
+ public void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
+ this.multiTenantConnectionProvider = multiTenantConnectionProvider;
+ }
+
+ /**
+ * Set a {@link CurrentTenantIdentifierResolver} to be passed on to the SessionFactory.
+ * @see LocalSessionFactoryBuilder#setCurrentTenantIdentifierResolver
+ */
+ public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
+ this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
+ }
+
+ /**
+ * Set Hibernate properties, such as "hibernate.dialect".
+ * Note: Do not specify a transaction provider here when using
+ * Spring-driven transactions. It is also advisable to omit connection
+ * provider settings and use a Spring-set DataSource instead.
+ * @see #setDataSource
+ */
+ public void setHibernateProperties(Properties hibernateProperties) {
+ this.hibernateProperties = hibernateProperties;
+ }
+
+ /**
+ * Return the Hibernate properties, if any. Mainly available for
+ * configuration through property paths that specify individual keys.
+ */
+ public Properties getHibernateProperties() {
+ if (this.hibernateProperties == null) {
+ this.hibernateProperties = new Properties();
+ }
+ return this.hibernateProperties;
+ }
+
+ /**
+ * Specify custom type filters for Spring-based scanning for entity classes.
+ * Default is to search all specified packages for classes annotated with
+ * {@code @jakarta.persistence.Entity}, {@code @jakarta.persistence.Embeddable}
+ * or {@code @jakarta.persistence.MappedSuperclass}.
+ * @see #setPackagesToScan
+ */
+ public void setEntityTypeFilters(TypeFilter... entityTypeFilters) {
+ this.entityTypeFilters = entityTypeFilters;
+ }
+
+ /**
+ * Specify annotated entity classes to register with this Hibernate SessionFactory.
+ * @see Configuration#addAnnotatedClass(Class)
+ */
+ public void setAnnotatedClasses(Class>... annotatedClasses) {
+ this.annotatedClasses = annotatedClasses;
+ }
+
+ /**
+ * Specify the names of annotated packages, for which package-level
+ * annotation metadata will be read.
+ * @see Configuration#addPackage(String)
+ */
+ public void setAnnotatedPackages(String... annotatedPackages) {
+ this.annotatedPackages = annotatedPackages;
+ }
+
+ /**
+ * Specify packages to search for autodetection of your entity classes in the
+ * classpath. This is analogous to Spring's component-scan feature
+ * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
+ */
+ public void setPackagesToScan(String... packagesToScan) {
+ this.packagesToScan = packagesToScan;
+ }
+
+ /**
+ * Specify an asynchronous executor for background bootstrapping,
+ * for example, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
+ * {@code SessionFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
+ * injection purposes instead of waiting for Hibernate's bootstrapping to complete.
+ * However, note that the first actual call to a {@code SessionFactory} method will
+ * then block until Hibernate's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code SessionFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * As of 6.2, Hibernate initialization is enforced before context refresh
+ * completion, waiting for asynchronous bootstrapping to complete by then.
+ * @since 4.3
+ * @see LocalSessionFactoryBuilder#buildSessionFactory(AsyncTaskExecutor)
+ */
+ public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
+ this.bootstrapExecutor = bootstrapExecutor;
+ }
+
+ /**
+ * Specify one or more Hibernate {@link Integrator} implementations to apply.
+ * This will only be applied for an internally built {@link MetadataSources}
+ * instance. {@link #setMetadataSources} effectively overrides such settings,
+ * with integrators to be applied to the externally built {@link MetadataSources}.
+ * @since 5.1
+ * @see #setMetadataSources
+ * @see BootstrapServiceRegistryBuilder#applyIntegrator
+ */
+ public void setHibernateIntegrators(Integrator... hibernateIntegrators) {
+ this.hibernateIntegrators = hibernateIntegrators;
+ }
+
+ /**
+ * Specify a Hibernate {@link MetadataSources} service to use (for example, reusing an
+ * existing one), potentially populated with a custom Hibernate bootstrap
+ * {@link org.hibernate.service.ServiceRegistry} as well.
+ * @since 4.3
+ * @see MetadataSources#MetadataSources(ServiceRegistry)
+ * @see BootstrapServiceRegistryBuilder#build()
+ */
+ public void setMetadataSources(MetadataSources metadataSources) {
+ this.metadataSourcesAccessed = true;
+ this.metadataSources = metadataSources;
+ }
+
+ /**
+ * Determine the Hibernate {@link MetadataSources} to use.
+ * Can also be externally called to initialize and pre-populate a {@link MetadataSources}
+ * instance which is then going to be used for {@link SessionFactory} building.
+ * @return the MetadataSources to use (never {@code null})
+ * @since 4.3
+ * @see LocalSessionFactoryBuilder#LocalSessionFactoryBuilder(DataSource, ResourceLoader, MetadataSources)
+ */
+ public MetadataSources getMetadataSources() {
+ this.metadataSourcesAccessed = true;
+ if (this.metadataSources == null) {
+ BootstrapServiceRegistryBuilder builder = new BootstrapServiceRegistryBuilder();
+ if (this.resourcePatternResolver != null) {
+ builder = builder.applyClassLoader(this.resourcePatternResolver.getClassLoader());
+ }
+ if (this.hibernateIntegrators != null) {
+ for (Integrator integrator : this.hibernateIntegrators) {
+ builder = builder.applyIntegrator(integrator);
+ }
+ }
+ this.metadataSources = new MetadataSources(builder.build());
+ }
+ return this.metadataSources;
+ }
+
+ /**
+ * Specify a Spring {@link ResourceLoader} to use for Hibernate metadata.
+ * @param resourceLoader the ResourceLoader to use (never {@code null})
+ */
+ @Override
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
+ }
+
+ /**
+ * Determine the Spring {@link ResourceLoader} to use for Hibernate metadata.
+ * @return the ResourceLoader to use (never {@code null})
+ * @since 4.3
+ */
+ public ResourceLoader getResourceLoader() {
+ if (this.resourcePatternResolver == null) {
+ this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
+ }
+ return this.resourcePatternResolver;
+ }
+
+ /**
+ * Accept the containing {@link BeanFactory}, registering corresponding Hibernate
+ * {@link org.hibernate.resource.beans.container.spi.BeanContainer} integration for
+ * it if possible. This requires a Spring {@link ConfigurableListableBeanFactory}.
+ * @since 5.1
+ * @see SpringBeanContainer
+ * @see LocalSessionFactoryBuilder#setBeanContainer
+ */
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ if (beanFactory instanceof ConfigurableListableBeanFactory clbf) {
+ this.beanFactory = clbf;
+ }
+ }
+
+
+ @Override
+ public void afterPropertiesSet() throws IOException {
+ if (this.metadataSources != null && !this.metadataSourcesAccessed) {
+ // Repeated initialization with no user-customized MetadataSources -> clear it.
+ this.metadataSources = null;
+ }
+
+ LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(
+ this.dataSource, getResourceLoader(), getMetadataSources());
+
+ if (this.configLocations != null) {
+ for (Resource resource : this.configLocations) {
+ // Load Hibernate configuration from given location.
+ sfb.configure(resource.getURL());
+ }
+ }
+
+ if (this.mappingResources != null) {
+ // Register given Hibernate mapping definitions, contained in resource files.
+ for (String mapping : this.mappingResources) {
+ Resource mr = new ClassPathResource(mapping.trim(), getResourceLoader().getClassLoader());
+ sfb.addInputStream(mr.getInputStream());
+ }
+ }
+
+ if (this.mappingLocations != null) {
+ // Register given Hibernate mapping definitions, contained in resource files.
+ for (Resource resource : this.mappingLocations) {
+ sfb.addInputStream(resource.getInputStream());
+ }
+ }
+
+ if (this.cacheableMappingLocations != null) {
+ // Register given cacheable Hibernate mapping definitions, read from the file system.
+ for (Resource resource : this.cacheableMappingLocations) {
+ sfb.addCacheableFile(resource.getFile());
+ }
+ }
+
+ if (this.mappingJarLocations != null) {
+ // Register given Hibernate mapping definitions, contained in jar files.
+ for (Resource resource : this.mappingJarLocations) {
+ sfb.addJar(resource.getFile());
+ }
+ }
+
+ if (this.mappingDirectoryLocations != null) {
+ // Register all Hibernate mapping definitions in the given directories.
+ for (Resource resource : this.mappingDirectoryLocations) {
+ File file = resource.getFile();
+ if (!file.isDirectory()) {
+ throw new IllegalArgumentException(
+ "Mapping directory location [" + resource + "] does not denote a directory");
+ }
+ sfb.addDirectory(file);
+ }
+ }
+
+ if (this.entityInterceptor != null) {
+ sfb.setInterceptor(this.entityInterceptor);
+ }
+
+ if (this.implicitNamingStrategy != null) {
+ sfb.setImplicitNamingStrategy(this.implicitNamingStrategy);
+ }
+
+ if (this.physicalNamingStrategy != null) {
+ sfb.setPhysicalNamingStrategy(this.physicalNamingStrategy);
+ }
+
+ if (this.jtaTransactionManager != null) {
+ sfb.setJtaTransactionManager(this.jtaTransactionManager);
+ }
+
+ if (this.beanFactory != null) {
+ sfb.setBeanContainer(this.beanFactory);
+ }
+
+ if (this.cacheRegionFactory != null) {
+ sfb.setCacheRegionFactory(this.cacheRegionFactory);
+ }
+
+ if (this.multiTenantConnectionProvider != null) {
+ sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
+ }
+
+ if (this.currentTenantIdentifierResolver != null) {
+ sfb.setCurrentTenantIdentifierResolver(this.currentTenantIdentifierResolver);
+ }
+
+ if (this.hibernateProperties != null) {
+ sfb.addProperties(this.hibernateProperties);
+ }
+
+ if (this.entityTypeFilters != null) {
+ sfb.setEntityTypeFilters(this.entityTypeFilters);
+ }
+
+ if (this.annotatedClasses != null) {
+ sfb.addAnnotatedClasses(this.annotatedClasses);
+ }
+
+ if (this.annotatedPackages != null) {
+ sfb.addPackages(this.annotatedPackages);
+ }
+
+ if (this.packagesToScan != null) {
+ sfb.scanPackages(this.packagesToScan);
+ }
+
+ // Build SessionFactory instance.
+ this.configuration = sfb;
+ this.sessionFactory = buildSessionFactory(sfb);
+ }
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ // Enforce completion of asynchronous Hibernate initialization before context refresh completion.
+ if (this.sessionFactory instanceof InfrastructureProxy proxy) {
+ proxy.getWrappedObject();
+ }
+ }
+
+ /**
+ * Subclasses can override this method to perform custom initialization
+ * of the SessionFactory instance, creating it via the given Configuration
+ * object that got prepared by this LocalSessionFactoryBean.
+ * The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory.
+ * A custom implementation could prepare the instance in a specific way (for example, applying
+ * a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass.
+ * @param sfb a LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean
+ * @return the SessionFactory instance
+ * @see LocalSessionFactoryBuilder#buildSessionFactory
+ */
+ protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
+ return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
+ sfb.buildSessionFactory());
+ }
+
+ /**
+ * Return the Hibernate Configuration object used to build the SessionFactory.
+ * Allows for access to configuration metadata stored there (rarely needed).
+ * @throws IllegalStateException if the Configuration object has not been initialized yet
+ */
+ public final Configuration getConfiguration() {
+ if (this.configuration == null) {
+ throw new IllegalStateException("Configuration not initialized yet");
+ }
+ return this.configuration;
+ }
+
+
+ @Override
+ @Nullable
+ public SessionFactory getObject() {
+ return this.sessionFactory;
+ }
+
+ @Override
+ public Class> getObjectType() {
+ return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+
+ @Override
+ public void destroy() {
+ if (this.sessionFactory != null) {
+ this.sessionFactory.close();
+ }
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/LocalSessionFactoryBuilder.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/LocalSessionFactoryBuilder.java
new file mode 100644
index 00000000000..5a2b2f65f0b
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/LocalSessionFactoryBuilder.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import javax.sql.DataSource;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.Entity;
+import jakarta.persistence.MappedSuperclass;
+import jakarta.transaction.TransactionManager;
+import org.hibernate.HibernateException;
+import org.hibernate.MappingException;
+import org.hibernate.SessionFactory;
+import org.hibernate.boot.MetadataSources;
+import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
+import org.hibernate.cache.spi.RegionFactory;
+import org.hibernate.cfg.AvailableSettings;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
+import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
+
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.InfrastructureProxy;
+import org.springframework.core.SpringProperties;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.ClassFormatException;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.jta.JtaTransactionManager;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * A Spring-provided extension of the standard Hibernate {@link Configuration} class,
+ * adding {@link SpringSessionContext} as a default and providing convenient ways
+ * to specify a JDBC {@link DataSource} and an application class loader.
+ *
+ * This is designed for programmatic use, for example, in {@code @Bean} factory methods;
+ * consider using {@link LocalSessionFactoryBean} for XML bean definition files.
+ * Typically combined with {@link HibernateTransactionManager} for declarative
+ * transactions against the {@code SessionFactory} and its JDBC {@code DataSource}.
+ *
+ * Compatible with Hibernate ORM 5.5/5.6, as of Spring Framework 6.0.
+ * This Hibernate-specific factory builder can also be a convenient way to set up
+ * a JPA {@code EntityManagerFactory} since the Hibernate {@code SessionFactory}
+ * natively exposes the JPA {@code EntityManagerFactory} interface as well now.
+ *
+ * This builder supports Hibernate {@code BeanContainer} integration,
+ * {@link MetadataSources} from custom {@link BootstrapServiceRegistryBuilder}
+ * setup, as well as other advanced Hibernate configuration options beyond the
+ * standard JPA bootstrap contract.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see HibernateTransactionManager
+ * @see LocalSessionFactoryBean
+ * @see #setBeanContainer
+ * @see #LocalSessionFactoryBuilder(DataSource, ResourceLoader, MetadataSources)
+ * @see BootstrapServiceRegistryBuilder
+ */
+@SuppressWarnings("serial")
+public class LocalSessionFactoryBuilder extends Configuration {
+
+ private static final String RESOURCE_PATTERN = "/**/*.class";
+
+ private static final String PACKAGE_INFO_SUFFIX = ".package-info";
+
+ private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = new TypeFilter[] {
+ new AnnotationTypeFilter(Entity.class, false),
+ new AnnotationTypeFilter(Embeddable.class, false),
+ new AnnotationTypeFilter(MappedSuperclass.class, false)};
+
+ private static final TypeFilter CONVERTER_TYPE_FILTER = new AnnotationTypeFilter(Converter.class, false);
+
+ private static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore";
+
+ private static final boolean shouldIgnoreClassFormatException =
+ SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME);
+
+
+ private final ResourcePatternResolver resourcePatternResolver;
+
+ private TypeFilter[] entityTypeFilters = DEFAULT_ENTITY_TYPE_FILTERS;
+
+
+ /**
+ * Create a new LocalSessionFactoryBuilder for the given DataSource.
+ * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
+ * (may be {@code null})
+ */
+ public LocalSessionFactoryBuilder(@Nullable DataSource dataSource) {
+ this(dataSource, new PathMatchingResourcePatternResolver());
+ }
+
+ /**
+ * Create a new LocalSessionFactoryBuilder for the given DataSource.
+ * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
+ * (may be {@code null})
+ * @param classLoader the ClassLoader to load application classes from
+ */
+ public LocalSessionFactoryBuilder(@Nullable DataSource dataSource, ClassLoader classLoader) {
+ this(dataSource, new PathMatchingResourcePatternResolver(classLoader));
+ }
+
+ /**
+ * Create a new LocalSessionFactoryBuilder for the given DataSource.
+ * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
+ * (may be {@code null})
+ * @param resourceLoader the ResourceLoader to load application classes from
+ */
+ public LocalSessionFactoryBuilder(@Nullable DataSource dataSource, ResourceLoader resourceLoader) {
+ this(dataSource, resourceLoader, new MetadataSources(
+ new BootstrapServiceRegistryBuilder().applyClassLoader(resourceLoader.getClassLoader()).build()));
+ }
+
+ /**
+ * Create a new LocalSessionFactoryBuilder for the given DataSource.
+ * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
+ * (may be {@code null})
+ * @param resourceLoader the ResourceLoader to load application classes from
+ * @param metadataSources the Hibernate MetadataSources service to use (for example, reusing an existing one)
+ * @since 4.3
+ */
+ public LocalSessionFactoryBuilder(
+ @Nullable DataSource dataSource, ResourceLoader resourceLoader, MetadataSources metadataSources) {
+
+ super(metadataSources);
+
+ getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName());
+ if (dataSource != null) {
+ getProperties().put(AvailableSettings.DATASOURCE, dataSource);
+ }
+ getProperties().put(AvailableSettings.CONNECTION_HANDLING,
+ PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD);
+
+ getProperties().put(AvailableSettings.CLASSLOADERS, Collections.singleton(resourceLoader.getClassLoader()));
+ this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
+ }
+
+
+ /**
+ * Set the Spring {@link JtaTransactionManager} or the JTA {@link TransactionManager}
+ * to be used with Hibernate, if any. Allows for using a Spring-managed transaction
+ * manager for Hibernate 5's session and cache synchronization, with the
+ * "hibernate.transaction.jta.platform" automatically set to it.
+ * A passed-in Spring {@link JtaTransactionManager} needs to contain a JTA
+ * {@link TransactionManager} reference to be usable here, except for the WebSphere
+ * case where we'll automatically set {@code WebSphereExtendedJtaPlatform} accordingly.
+ * Note: If this is set, the Hibernate settings should not contain a JTA platform
+ * setting to avoid meaningless double configuration.
+ */
+ public LocalSessionFactoryBuilder setJtaTransactionManager(Object jtaTransactionManager) {
+ Assert.notNull(jtaTransactionManager, "Transaction manager reference must not be null");
+
+ if (jtaTransactionManager instanceof JtaTransactionManager springJtaTm) {
+ boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader());
+ if (webspherePresent) {
+ getProperties().put(AvailableSettings.JTA_PLATFORM,
+ "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform");
+ }
+ else {
+ if (springJtaTm.getTransactionManager() == null) {
+ throw new IllegalArgumentException(
+ "Can only apply JtaTransactionManager which has a TransactionManager reference set");
+ }
+ getProperties().put(AvailableSettings.JTA_PLATFORM,
+ new ConfigurableJtaPlatform(springJtaTm.getTransactionManager(), springJtaTm.getUserTransaction(),
+ springJtaTm.getTransactionSynchronizationRegistry()));
+ }
+ }
+ else if (jtaTransactionManager instanceof TransactionManager jtaTm) {
+ getProperties().put(AvailableSettings.JTA_PLATFORM,
+ new ConfigurableJtaPlatform(jtaTm, null, null));
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Unknown transaction manager type: " + jtaTransactionManager.getClass().getName());
+ }
+
+ getProperties().put(AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta");
+ getProperties().put(AvailableSettings.CONNECTION_HANDLING,
+ PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT);
+
+ return this;
+ }
+
+ /**
+ * Set a Hibernate {@link org.hibernate.resource.beans.container.spi.BeanContainer}
+ * for the given Spring {@link ConfigurableListableBeanFactory}.
+ * This enables autowiring of Hibernate attribute converters and entity listeners.
+ * @since 5.1
+ * @see SpringBeanContainer
+ * @see AvailableSettings#BEAN_CONTAINER
+ */
+ public LocalSessionFactoryBuilder setBeanContainer(ConfigurableListableBeanFactory beanFactory) {
+ getProperties().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
+ return this;
+ }
+
+ /**
+ * Set the Hibernate {@link RegionFactory} to use for the SessionFactory.
+ * Allows for using a Spring-managed {@code RegionFactory} instance.
+ * Note: If this is set, the Hibernate settings should not define a
+ * cache provider to avoid meaningless double configuration.
+ * @since 5.1
+ * @see AvailableSettings#CACHE_REGION_FACTORY
+ */
+ public LocalSessionFactoryBuilder setCacheRegionFactory(RegionFactory cacheRegionFactory) {
+ getProperties().put(AvailableSettings.CACHE_REGION_FACTORY, cacheRegionFactory);
+ return this;
+ }
+
+ /**
+ * Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory.
+ * @since 4.3
+ * @see AvailableSettings#MULTI_TENANT_CONNECTION_PROVIDER
+ */
+ public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
+ getProperties().put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
+ return this;
+ }
+
+ /**
+ * Overridden to reliably pass a {@link CurrentTenantIdentifierResolver} to the SessionFactory.
+ * @since 4.3.2
+ * @see AvailableSettings#MULTI_TENANT_IDENTIFIER_RESOLVER
+ */
+ @Override
+ public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
+ getProperties().put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
+ super.setCurrentTenantIdentifierResolver(currentTenantIdentifierResolver);
+ }
+
+ /**
+ * Specify custom type filters for Spring-based scanning for entity classes.
+ * Default is to search all specified packages for classes annotated with
+ * {@code @jakarta.persistence.Entity}, {@code @jakarta.persistence.Embeddable}
+ * or {@code @jakarta.persistence.MappedSuperclass}.
+ * @see #scanPackages
+ */
+ public LocalSessionFactoryBuilder setEntityTypeFilters(TypeFilter... entityTypeFilters) {
+ this.entityTypeFilters = entityTypeFilters;
+ return this;
+ }
+
+ /**
+ * Add the given annotated classes in a batch.
+ * @see #addAnnotatedClass
+ * @see #scanPackages
+ */
+ public LocalSessionFactoryBuilder addAnnotatedClasses(Class>... annotatedClasses) {
+ for (Class> annotatedClass : annotatedClasses) {
+ addAnnotatedClass(annotatedClass);
+ }
+ return this;
+ }
+
+ /**
+ * Add the given annotated packages in a batch.
+ * @see #addPackage
+ * @see #scanPackages
+ */
+ public LocalSessionFactoryBuilder addPackages(String... annotatedPackages) {
+ for (String annotatedPackage : annotatedPackages) {
+ addPackage(annotatedPackage);
+ }
+ return this;
+ }
+
+ /**
+ * Perform Spring-based scanning for entity classes, registering them
+ * as annotated classes with this {@code Configuration}.
+ * @param packagesToScan one or more Java package names
+ * @throws HibernateException if scanning fails for any reason
+ */
+ @SuppressWarnings("unchecked")
+ public LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws HibernateException {
+ Set {@code SessionFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
+ * injection purposes instead of waiting for Hibernate's bootstrapping to complete.
+ * However, note that the first actual call to a {@code SessionFactory} method will
+ * then block until Hibernate's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code SessionFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * @since 4.3
+ * @see #buildSessionFactory()
+ */
+ public SessionFactory buildSessionFactory(AsyncTaskExecutor bootstrapExecutor) {
+ Assert.notNull(bootstrapExecutor, "AsyncTaskExecutor must not be null");
+ return (SessionFactory) Proxy.newProxyInstance(this.resourcePatternResolver.getClassLoader(),
+ new Class>[] {SessionFactoryImplementor.class, InfrastructureProxy.class},
+ new BootstrapSessionFactoryInvocationHandler(bootstrapExecutor));
+ }
+
+
+ /**
+ * Proxy invocation handler for background bootstrapping, only enforcing
+ * a fully initialized target {@code SessionFactory} when actually needed.
+ * @since 4.3
+ */
+ private class BootstrapSessionFactoryInvocationHandler implements InvocationHandler {
+
+ private final Future Used internally by {@link HibernateTransactionManager}.
+ * Can also be used directly in application code.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see HibernateExceptionTranslator
+ * @see HibernateTransactionManager
+ */
+public abstract class SessionFactoryUtils {
+
+ /**
+ * Order value for TransactionSynchronization objects that clean up Hibernate Sessions.
+ * Returns {@code DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100}
+ * to execute Session cleanup before JDBC Connection cleanup, if any.
+ * @see DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
+ */
+ public static final int SESSION_SYNCHRONIZATION_ORDER =
+ DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
+
+ static final Log logger = LogFactory.getLog(SessionFactoryUtils.class);
+
+
+ /**
+ * Trigger a flush on the given Hibernate Session, converting regular
+ * {@link HibernateException} instances as well as Hibernate 5.2's
+ * {@link PersistenceException} wrappers accordingly.
+ * @param session the Hibernate Session to flush
+ * @param synch whether this flush is triggered by transaction synchronization
+ * @throws DataAccessException in case of flush failures
+ * @since 4.3.2
+ */
+ static void flush(Session session, boolean synch) throws DataAccessException {
+ if (synch) {
+ logger.debug("Flushing Hibernate Session on transaction synchronization");
+ }
+ else {
+ logger.debug("Flushing Hibernate Session on explicit request");
+ }
+ try {
+ session.flush();
+ }
+ catch (HibernateException ex) {
+ throw convertHibernateAccessException(ex);
+ }
+ catch (PersistenceException ex) {
+ if (ex.getCause() instanceof HibernateException hibernateException) {
+ throw convertHibernateAccessException(hibernateException);
+ }
+ throw ex;
+ }
+
+ }
+
+ /**
+ * Perform actual closing of the Hibernate Session,
+ * catching and logging any cleanup exceptions thrown.
+ * @param session the Hibernate Session to close (may be {@code null})
+ * @see Session#close()
+ */
+ public static void closeSession(@Nullable Session session) {
+ if (session != null) {
+ try {
+ if (session.isOpen()) {
+ session.close();
+ }
+ }
+ catch (Throwable ex) {
+ logger.error("Failed to release Hibernate Session", ex);
+ }
+ }
+ }
+
+ /**
+ * Determine the DataSource of the given SessionFactory.
+ * @param sessionFactory the SessionFactory to check
+ * @return the DataSource, or {@code null} if none found
+ * @see ConnectionProvider
+ */
+ @Nullable
+ public static DataSource getDataSource(SessionFactory sessionFactory) {
+ Method getProperties = ClassUtils.getMethodIfAvailable(sessionFactory.getClass(), "getProperties");
+ if (getProperties != null) {
+ Map, ?> props = (Map, ?>) ReflectionUtils.invokeMethod(getProperties, sessionFactory);
+ if (props != null) {
+ Object dataSourceValue = props.get(Environment.DATASOURCE);
+ if (dataSourceValue instanceof DataSource dataSource) {
+ return dataSource;
+ }
+ }
+ }
+ if (sessionFactory instanceof SessionFactoryImplementor sfi) {
+ try {
+ ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class);
+ if (cp != null) {
+ return cp.unwrap(DataSource.class);
+ }
+ }
+ catch (UnknownServiceException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("No ConnectionProvider found - cannot determine DataSource for SessionFactory: " + ex);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convert the given HibernateException to an appropriate exception
+ * from the {@code org.springframework.dao} hierarchy.
+ * @param ex the HibernateException that occurred
+ * @return the corresponding DataAccessException instance
+ * @see HibernateExceptionTranslator#convertHibernateAccessException
+ * @see HibernateTransactionManager#convertHibernateAccessException
+ */
+ public static DataAccessException convertHibernateAccessException(HibernateException ex) {
+ if (ex instanceof JDBCConnectionException) {
+ return new DataAccessResourceFailureException(ex.getMessage(), ex);
+ }
+ if (ex instanceof SQLGrammarException hibJdbcEx) {
+ return new InvalidDataAccessResourceUsageException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+ }
+ if (ex instanceof QueryTimeoutException hibJdbcEx) {
+ return new org.springframework.dao.QueryTimeoutException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+ }
+ if (ex instanceof LockAcquisitionException hibJdbcEx) {
+ return new CannotAcquireLockException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+ }
+ if (ex instanceof PessimisticLockException hibJdbcEx) {
+ return new PessimisticLockingFailureException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+ }
+ if (ex instanceof ConstraintViolationException hibJdbcEx) {
+ return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() +
+ "]; constraint [" + hibJdbcEx.getConstraintName() + "]", ex);
+ }
+ if (ex instanceof DataException hibJdbcEx) {
+ return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + hibJdbcEx.getSQL() + "]", ex);
+ }
+ if (ex instanceof JDBCException hibJdbcEx) {
+ return new HibernateJdbcException(hibJdbcEx);
+ }
+ // end of JDBCException (subclass) handling
+
+ if (ex instanceof QueryException queryException) {
+ return new HibernateQueryException(queryException);
+ }
+ if (ex instanceof NonUniqueResultException) {
+ return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);
+ }
+ if (ex instanceof NonUniqueObjectException) {
+ return new DuplicateKeyException(ex.getMessage(), ex);
+ }
+ if (ex instanceof PropertyValueException) {
+ return new DataIntegrityViolationException(ex.getMessage(), ex);
+ }
+ if (ex instanceof PersistentObjectException) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+ if (ex instanceof TransientObjectException) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+ if (ex instanceof ObjectDeletedException) {
+ return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
+ }
+ if (ex instanceof UnresolvableObjectException unresolvableObjectException) {
+ return new HibernateObjectRetrievalFailureException(unresolvableObjectException);
+ }
+ if (ex instanceof WrongClassException wrongClassException) {
+ return new HibernateObjectRetrievalFailureException(wrongClassException);
+ }
+ if (ex instanceof StaleObjectStateException staleObjectStateException) {
+ return new HibernateOptimisticLockingFailureException(staleObjectStateException);
+ }
+ if (ex instanceof StaleStateException staleStateException) {
+ return new HibernateOptimisticLockingFailureException(staleStateException);
+ }
+ if (ex instanceof OptimisticEntityLockException optimisticEntityLockException) {
+ return new HibernateOptimisticLockingFailureException(optimisticEntityLockException);
+ }
+ if (ex instanceof PessimisticEntityLockException) {
+ if (ex.getCause() instanceof LockAcquisitionException) {
+ return new CannotAcquireLockException(ex.getMessage(), ex.getCause());
+ }
+ return new PessimisticLockingFailureException(ex.getMessage(), ex);
+ }
+
+ // fallback
+ return new HibernateSystemException(ex);
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/SessionHolder.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/SessionHolder.java
new file mode 100644
index 00000000000..35f1e2338d8
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/SessionHolder.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import org.hibernate.FlushMode;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+
+import org.springframework.lang.Nullable;
+import org.springframework.orm.jpa.EntityManagerHolder;
+
+/**
+ * Resource holder wrapping a Hibernate {@link Session} (plus an optional {@link Transaction}).
+ * {@link HibernateTransactionManager} binds instances of this class to the thread,
+ * for a given {@link org.hibernate.SessionFactory}. Extends {@link EntityManagerHolder}
+ * as of 5.1, automatically exposing an {@code EntityManager} handle on Hibernate 5.2+.
+ *
+ * Note: This is an SPI class, not intended to be used by applications.
+ *
+ * @author Juergen Hoeller
+ * @since 4.2
+ * @see HibernateTransactionManager
+ * @see SessionFactoryUtils
+ */
+public class SessionHolder extends EntityManagerHolder {
+
+ @Nullable
+ private Transaction transaction;
+
+ @Nullable
+ private FlushMode previousFlushMode;
+
+
+ public SessionHolder(Session session) {
+ super(session);
+ }
+
+
+ public Session getSession() {
+ return (Session) getEntityManager();
+ }
+
+ public void setTransaction(@Nullable Transaction transaction) {
+ this.transaction = transaction;
+ setTransactionActive(transaction != null);
+ }
+
+ @Nullable
+ public Transaction getTransaction() {
+ return this.transaction;
+ }
+
+ public void setPreviousFlushMode(@Nullable FlushMode previousFlushMode) {
+ this.previousFlushMode = previousFlushMode;
+ }
+
+ @Nullable
+ public FlushMode getPreviousFlushMode() {
+ return this.previousFlushMode;
+ }
+
+
+ @Override
+ public void clear() {
+ super.clear();
+ this.transaction = null;
+ this.previousFlushMode = null;
+ }
+
+}
diff --git a/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/SpringBeanContainer.java b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/SpringBeanContainer.java
new file mode 100644
index 00000000000..4dda1852ce3
--- /dev/null
+++ b/grails-data-hibernate5/spring-orm/src/main/java/org/grails/orm/hibernate/support/hibernate5/SpringBeanContainer.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2002-present 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
+ *
+ * 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.support.hibernate5;
+
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.resource.beans.container.spi.BeanContainer;
+import org.hibernate.resource.beans.container.spi.ContainedBean;
+import org.hibernate.resource.beans.spi.BeanInstanceProducer;
+import org.hibernate.type.spi.TypeBootstrapContext;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+/**
+ * Spring's implementation of Hibernate's {@link BeanContainer} SPI,
+ * delegating to a Spring {@link ConfigurableListableBeanFactory}.
+ *
+ * Auto-configured by {@link LocalSessionFactoryBean#setBeanFactory},
+ * programmatically supported via {@link LocalSessionFactoryBuilder#setBeanContainer},
+ * and manually configurable through a "hibernate.resource.beans.container" entry
+ * in JPA properties, for example:
+ *
+ * >) session -> {
+ Criteria criteria = session.createCriteria(entityClass);
+ criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
+ prepareCriteria(criteria);
+ return criteria.list();
+ }));
+ }
+
+ @Override
+ public void load(Object entity, Serializable id) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ session.load(entity, id);
+ return null;
+ });
+ }
+
+ @Override
+ public void refresh(Object entity) throws DataAccessException {
+ refresh(entity, null);
+ }
+
+ @Override
+ public void refresh(Object entity, @Nullable LockMode lockMode) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ if (lockMode != null) {
+ session.refresh(entity, new LockOptions(lockMode));
+ }
+ else {
+ session.refresh(entity);
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public boolean contains(Object entity) throws DataAccessException {
+ Boolean result = executeWithNativeSession(session -> session.contains(entity));
+ Assert.state(result != null, "No contains result");
+ return result;
+ }
+
+ @Override
+ public void evict(Object entity) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ session.evict(entity);
+ return null;
+ });
+ }
+
+ @Override
+ public void initialize(Object proxy) throws DataAccessException {
+ try {
+ Hibernate.initialize(proxy);
+ }
+ catch (HibernateException ex) {
+ throw SessionFactoryUtils.convertHibernateAccessException(ex);
+ }
+ }
+
+ @Override
+ public Filter enableFilter(String filterName) throws IllegalStateException {
+ Session session = obtainSessionFactory().getCurrentSession();
+ Filter filter = session.getEnabledFilter(filterName);
+ if (filter == null) {
+ filter = session.enableFilter(filterName);
+ }
+ return filter;
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience methods for storing individual objects
+ //-------------------------------------------------------------------------
+
+ @Override
+ public void lock(Object entity, LockMode lockMode) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ session.buildLockRequest(new LockOptions(lockMode)).lock(entity);
+ return null;
+ });
+ }
+
+ @Override
+ public void lock(String entityName, Object entity, LockMode lockMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(session -> {
+ session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity);
+ return null;
+ });
+ }
+
+ @Override
+ public Serializable save(Object entity) throws DataAccessException {
+ return nonNull(executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ return session.save(entity);
+ }));
+ }
+
+ @Override
+ public Serializable save(String entityName, Object entity) throws DataAccessException {
+ return nonNull(executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ return session.save(entityName, entity);
+ }));
+ }
+
+ @Override
+ public void update(Object entity) throws DataAccessException {
+ update(entity, null);
+ }
+
+ @Override
+ public void update(Object entity, @Nullable LockMode lockMode) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.update(entity);
+ if (lockMode != null) {
+ session.buildLockRequest(new LockOptions(lockMode)).lock(entity);
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public void update(String entityName, Object entity) throws DataAccessException {
+ update(entityName, entity, null);
+ }
+
+ @Override
+ public void update(String entityName, Object entity, @Nullable LockMode lockMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.update(entityName, entity);
+ if (lockMode != null) {
+ session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity);
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public void saveOrUpdate(Object entity) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.saveOrUpdate(entity);
+ return null;
+ });
+ }
+
+ @Override
+ public void saveOrUpdate(String entityName, Object entity) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.saveOrUpdate(entityName, entity);
+ return null;
+ });
+ }
+
+ @Override
+ public void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.replicate(entity, replicationMode);
+ return null;
+ });
+ }
+
+ @Override
+ public void replicate(String entityName, Object entity, ReplicationMode replicationMode)
+ throws DataAccessException {
+
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.replicate(entityName, entity, replicationMode);
+ return null;
+ });
+ }
+
+ @Override
+ public void persist(Object entity) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.persist(entity);
+ return null;
+ });
+ }
+
+ @Override
+ public void persist(String entityName, Object entity) throws DataAccessException {
+ executeWithNativeSession(session -> {
+ checkWriteOperationAllowed(session);
+ session.persist(entityName, entity);
+ return null;
+ });
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public
>) session -> {
+ Criteria executableCriteria = criteria.getExecutableCriteria(session);
+ prepareCriteria(executableCriteria);
+ if (firstResult >= 0) {
+ executableCriteria.setFirstResult(firstResult);
+ }
+ if (maxResults > 0) {
+ executableCriteria.setMaxResults(maxResults);
+ }
+ return executableCriteria.list();
+ }));
+ }
+
+ @Override
+ public
>) session -> {
+ Criteria executableCriteria = (entityName != null ?
+ session.createCriteria(entityName) : session.createCriteria(exampleEntity.getClass()));
+ executableCriteria.add(Example.create(exampleEntity));
+ prepareCriteria(executableCriteria);
+ if (firstResult >= 0) {
+ executableCriteria.setFirstResult(firstResult);
+ }
+ if (maxResults > 0) {
+ executableCriteria.setMaxResults(maxResults);
+ }
+ return executableCriteria.list();
+ }));
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience finder methods for HQL strings
+ //-------------------------------------------------------------------------
+
+ @Deprecated
+ @Override
+ public List> find(String queryString, @Nullable Object... values) throws DataAccessException {
+ return nonNull(executeWithNativeSession((HibernateCallback
>) session -> {
+ Query> queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ queryObject.setParameter(i, values[i]);
+ }
+ }
+ return queryObject.list();
+ }));
+ }
+
+ @Deprecated
+ @Override
+ public List> findByNamedParam(String queryString, String paramName, Object value)
+ throws DataAccessException {
+
+ return findByNamedParam(queryString, new String[] {paramName}, new Object[] {value});
+ }
+
+ @Deprecated
+ @Override
+ public List> findByNamedParam(String queryString, String[] paramNames, Object[] values)
+ throws DataAccessException {
+
+ if (paramNames.length != values.length) {
+ throw new IllegalArgumentException("Length of paramNames array must match length of values array");
+ }
+ return nonNull(executeWithNativeSession((HibernateCallback
>) session -> {
+ Query> queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ for (int i = 0; i < values.length; i++) {
+ applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
+ }
+ return queryObject.list();
+ }));
+ }
+
+ @Deprecated
+ @Override
+ public List> findByValueBean(String queryString, Object valueBean) throws DataAccessException {
+ return nonNull(executeWithNativeSession((HibernateCallback
>) session -> {
+ Query> queryObject = session.createQuery(queryString);
+ prepareQuery(queryObject);
+ queryObject.setProperties(valueBean);
+ return queryObject.list();
+ }));
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience finder methods for named queries
+ //-------------------------------------------------------------------------
+
+ @Deprecated
+ @Override
+ public List> findByNamedQuery(String queryName, @Nullable Object... values) throws DataAccessException {
+ return nonNull(executeWithNativeSession((HibernateCallback
>) session -> {
+ Query> queryObject = session.getNamedQuery(queryName);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ queryObject.setParameter(i, values[i]);
+ }
+ }
+ return queryObject.list();
+ }));
+ }
+
+ @Deprecated
+ @Override
+ public List> findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
+ throws DataAccessException {
+
+ return findByNamedQueryAndNamedParam(queryName, new String[] {paramName}, new Object[] {value});
+ }
+
+ @Deprecated
+ @Override
+ @SuppressWarnings("NullAway")
+ public List> findByNamedQueryAndNamedParam(
+ String queryName, @Nullable String[] paramNames, @Nullable Object[] values)
+ throws DataAccessException {
+
+ if (values != null && (paramNames == null || paramNames.length != values.length)) {
+ throw new IllegalArgumentException("Length of paramNames array must match length of values array");
+ }
+ return nonNull(executeWithNativeSession((HibernateCallback
>) session -> {
+ Query> queryObject = session.getNamedQuery(queryName);
+ prepareQuery(queryObject);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
+ }
+ }
+ return queryObject.list();
+ }));
+ }
+
+ @Deprecated
+ @Override
+ public List> findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException {
+ return nonNull(executeWithNativeSession((HibernateCallback
>) session -> {
+ Query> queryObject = session.getNamedQuery(queryName);
+ prepareQuery(queryObject);
+ queryObject.setProperties(valueBean);
+ return queryObject.list();
+ }));
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Convenience query methods for iteration and bulk updates/deletes
+ //-------------------------------------------------------------------------
+
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public Iterator> iterate(String queryString, @Nullable Object... values) throws DataAccessException {
+ return nonNull(executeWithNativeSession((HibernateCallback
+ * <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
+ * ...
+ * <property name="jpaPropertyMap">
+ * <map>
+ * <entry key="hibernate.resource.beans.container">
+ * <bean class="org.springframework.orm.hibernate5.SpringBeanContainer"/>
+ * </entry>
+ * </map>
+ * </property>
+ * </bean>
+ *
+ * Or in Java-based JPA configuration:
+ *
+ *
+ * LocalContainerEntityManagerFactoryBean emfb = ...
+ * emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
+ *
+ *
+ * Please note that Spring's {@link LocalSessionFactoryBean} is an immediate alternative
+ * to {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean} for
+ * common JPA purposes: The Hibernate {@code SessionFactory} will natively expose the JPA
+ * {@code EntityManagerFactory} interface as well, and Hibernate {@code BeanContainer}
+ * integration will be registered out of the box.
+ *
+ * @author Juergen Hoeller
+ * @since 5.1
+ * @see LocalSessionFactoryBean#setBeanFactory
+ * @see LocalSessionFactoryBuilder#setBeanContainer
+ * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setJpaPropertyMap
+ * @see org.hibernate.cfg.AvailableSettings#BEAN_CONTAINER
+ */
+public final class SpringBeanContainer implements BeanContainer {
+
+ private static final Log logger = LogFactory.getLog(SpringBeanContainer.class);
+
+ private final ConfigurableListableBeanFactory beanFactory;
+
+ private final Map