diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index fb3a23f523b..d971fd1e64e 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -9,14 +9,14 @@ javaPlatform { dependencies { api(platform("com.fasterxml.jackson:jackson-bom:2.21.5")) api(platform("io.micrometer:micrometer-bom:1.18.0-M1")) - api(platform("io.netty:netty-bom:4.2.17.Final")) + api(platform("io.netty:netty-bom:4.2.18.Final")) api(platform("io.projectreactor:reactor-bom:2026.0.0-SNAPSHOT")) api(platform("io.rsocket:rsocket-bom:1.1.5")) api(platform("org.apache.groovy:groovy-bom:5.1.1")) api(platform("org.apache.logging.log4j:log4j-bom:2.26.1")) api(platform("org.assertj:assertj-bom:3.27.7")) - api(platform("org.eclipse.jetty:jetty-bom:12.1.12")) - api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.12")) + api(platform("org.eclipse.jetty:jetty-bom:12.1.13")) + api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.13")) api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.11.0")) api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.11.0")) api(platform("org.junit:junit-bom:6.1.2")) @@ -31,7 +31,7 @@ dependencies { api("com.google.code.findbugs:findbugs:3.0.1") api("com.google.code.findbugs:jsr305:3.0.2") api("com.google.code.gson:gson:2.14.0") - api("com.google.protobuf:protobuf-java-util:4.35.1") + api("com.google.protobuf:protobuf-java-util:4.36.1") api("com.h2database:h2:2.4.240") api("com.jayway.jsonpath:json-path:2.10.0") api("com.networknt:json-schema-validator:1.5.3") @@ -96,10 +96,10 @@ dependencies { api("org.apache.httpcomponents.client5:httpclient5:5.6") api("org.apache.httpcomponents.core5:httpcore5-reactive:5.4.2") api("org.apache.poi:poi-ooxml:5.5.1") - api("org.apache.tomcat.embed:tomcat-embed-core:11.0.24") - api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24") - api("org.apache.tomcat:tomcat-util:11.0.24") - api("org.apache.tomcat:tomcat-websocket:11.0.24") + api("org.apache.tomcat.embed:tomcat-embed-core:11.0.25") + api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.25") + api("org.apache.tomcat:tomcat-util:11.0.25") + api("org.apache.tomcat:tomcat-websocket:11.0.25") api("org.aspectj:aspectjrt:1.9.25") api("org.aspectj:aspectjtools:1.9.25") api("org.aspectj:aspectjweaver:1.9.25") diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java index fdbc4e41576..474b5f69c7b 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java @@ -54,6 +54,13 @@ public class EntityManagerHolder extends ResourceHolderSupport { this.entityAgent = entityAgent; } + protected EntityManagerHolder(EntityManagerHolder original) { + this.entityManager = original.entityManager; + this.entityAgent = original.entityAgent; + this.transactionActive = original.transactionActive; + this.savepointManager = original.savepointManager; + } + void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java index 55a488eb2d8..5f3436e250e 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java @@ -44,6 +44,7 @@ import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import org.springframework.orm.jpa.EntityManagerFactoryUtils; +import org.springframework.orm.jpa.EntityManagerHolder; import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.InvalidIsolationLevelException; @@ -391,9 +392,17 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana txObject.setSavepointAllowed(isNestedTransactionAllowed()); SessionFactory sessionFactory = obtainSessionFactory(); - SessionHolder sessionHolder = - (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); - if (sessionHolder != null) { + Object resource = TransactionSynchronizationManager.getResource(sessionFactory); + if (resource instanceof EntityManagerHolder emHolder) { + SessionHolder sessionHolder; + if (emHolder instanceof SessionHolder originalSessionHolder) { + sessionHolder = originalSessionHolder; + } + else { + sessionHolder = new SessionHolder(emHolder); + TransactionSynchronizationManager.unbindResource(sessionFactory); + TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); + } if (logger.isDebugEnabled()) { logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction"); } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java index 29c14516ccc..bccb416aea5 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java @@ -16,6 +16,7 @@ package org.springframework.orm.jpa.hibernate; +import jakarta.persistence.EntityManager; import org.hibernate.FlushMode; import org.hibernate.Session; import org.hibernate.StatelessSession; @@ -49,10 +50,14 @@ public class SessionHolder extends EntityManagerHolder { } SessionHolder(StatelessSession session) { - super(null); + super((EntityManager) null); setStatelessSession(session); } + SessionHolder(EntityManagerHolder emHolder) { + super(emHolder); + } + void setSession(Session session) { this.entityManager = session; diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionContext.java b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionContext.java index cde4bef7bac..56c5322b069 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionContext.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionContext.java @@ -187,7 +187,16 @@ public class SpringSessionContext implements CurrentSessionContext { if (value instanceof StatelessSession statelessSession) { return statelessSession; } - if (value instanceof SessionHolder sessionHolder) { + else if (value instanceof EntityManagerHolder emHolder) { + SessionHolder sessionHolder; + if (emHolder instanceof SessionHolder originalSessionHolder) { + sessionHolder = originalSessionHolder; + } + else { + sessionHolder = new SessionHolder(emHolder); + TransactionSynchronizationManager.unbindResource(sessionFactory); + TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); + } if (sessionHolder.hasStatelessSession()) { return sessionHolder.getStatelessSession(); } @@ -216,7 +225,7 @@ public class SpringSessionContext implements CurrentSessionContext { return session; } - throw new HibernateException("Could not obtain transaction-synchronized Session for current thread"); + throw new HibernateException("Could not obtain transaction-synchronized StatelessSession for current thread"); } private static void bindSessionHolder(SessionFactory sessionFactory, SessionHolder holder) { diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java index 7370b777fae..8633af8d885 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java @@ -32,7 +32,10 @@ import org.springframework.context.ApplicationContext; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.orm.jpa.AbstractContainerEntityManagerFactoryIntegrationTests; import org.springframework.orm.jpa.EntityManagerFactoryInfo; +import org.springframework.orm.jpa.EntityManagerHolder; +import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.domain.Person; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -150,6 +153,61 @@ class HibernateNativeEntityManagerFactoryIntegrationTests extends AbstractContai }); } + @Test + void withJpaTransactionManager() { + String firstName = "Tony"; + insertPerson(firstName); + + Query q = statelessSession.createQuery("select p from Person as p", Person.class); + assertThat(q.getResultList()).hasSize(1); + assertThat(q.getResultList().get(0).getFirstName()).isEqualTo(firstName); + + endTransaction(); + + JpaTransactionManager dstm = new JpaTransactionManager(sessionFactory); + new TransactionTemplate(dstm).execute(status -> { + insertPerson(firstName); + Query q2 = statelessSession.createQuery("select p from Person as p", Person.class); + assertThat(q2.getResultList()).hasSize(1); + assertThat(q2.getResultList().get(0).getFirstName()).isEqualTo(firstName); + Query q3 = sharedSession.createQuery("select p from Person as p", Person.class); + assertThat(q3.getResultList()).hasSize(1); + assertThat(q3.getResultList().get(0).getFirstName()).isEqualTo(firstName); + status.setRollbackOnly(); + return null; + }); + } + + @Test + void withPreBoundEntityManager() { + String firstName = "Tony"; + insertPerson(firstName); + + Query q = statelessSession.createQuery("select p from Person as p", Person.class); + assertThat(q.getResultList()).hasSize(1); + assertThat(q.getResultList().get(0).getFirstName()).isEqualTo(firstName); + + endTransaction(); + + Session session = sessionFactory.openSession(); + TransactionSynchronizationManager.bindResource(sessionFactory, new EntityManagerHolder(session)); + + startNewTransaction(); + + insertPerson(firstName); + Query q2 = statelessSession.createQuery("select p from Person as p", Person.class); + assertThat(q2.getResultList()).hasSize(1); + assertThat(q2.getResultList().get(0).getFirstName()).isEqualTo(firstName); + Query q3 = sharedSession.createQuery("select p from Person as p", Person.class); + assertThat(q3.getResultList()).hasSize(1); + assertThat(q3.getResultList().get(0).getFirstName()).isEqualTo(firstName); + + endTransaction(); + + session.close(); + TransactionSynchronizationManager.unbindResource(sessionFactory); + } + @Test // SPR-16956 void readOnly() { assertThat(sessionFactory.getCurrentSession().getHibernateFlushMode()).isSameAs(FlushMode.AUTO);