diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java index 94126be6c39..a5ba314290c 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java @@ -18,6 +18,7 @@ package org.springframework.orm.jpa; import java.io.Serializable; import java.sql.SQLException; +import java.util.Map; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; @@ -113,6 +114,27 @@ public class DefaultJpaDialect implements JpaDialect, Serializable { throws PersistenceException, SQLException { } + /** + * Derive a new {@code EntityAgent} from the given {@code EntityManager} if possible, + * sharing the transactional context. + *

NOTE: This method is designed for Spring's early JPA 4.0 support. + * For JPA 3.2 compatibility, the return type cannot be enforced as + * {@code jakarta.persistence.EntityAgent}. Subclasses should override it with + * their specific EntityAgent type if possible, or otherwise just EntityAgent, + * as a covariant return type. This will make it forward-compatible with a + * future variant of this method in the {@link JpaDialect} interface itself. + * @param entityManager the current JPA EntityManager + * @param properties the properties for the EntityAgent, if any + * @return the new EntityAgent instance, or {@code null} if none can be derived + * @throws jakarta.persistence.PersistenceException if thrown by JPA methods + * @since 7.0.4 + */ + public @Nullable Object deriveEntityAgent(EntityManager entityManager, @Nullable Map properties) + throws PersistenceException { + + return null; + } + //----------------------------------------------------------------------------------- // Hook for exception translation (used by JpaTransactionManager) diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java index 43d1f5e8d1c..3b42c1fde59 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java @@ -334,7 +334,15 @@ public abstract class EntityManagerFactoryUtils { // Create a new EntityManager for use within the current transaction. logger.debug("Opening JPA EntityAgent"); - Object entityAgent = createEntityAgent(emf, properties); + Object entityAgent = null; + if (emHolder != null && emf instanceof EntityManagerFactoryInfo info && + info.getJpaDialect() instanceof DefaultJpaDialect defaultJpaDialect) { + // For JpaTransactionManager: share transaction context with primary EntityManager. + entityAgent = defaultJpaDialect.deriveEntityAgent(emHolder.getEntityManager(), properties); + } + if (entityAgent == null) { + entityAgent = createEntityAgent(emf, properties); + } if (emHolder != null) { emHolder.setEntityAgent(entityAgent); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java index 4c21adde1bc..8838ee56d7e 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java @@ -16,8 +16,10 @@ package org.springframework.orm.jpa.vendor; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; +import java.util.Map; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; @@ -25,6 +27,7 @@ import org.hibernate.ConnectionReleaseMode; import org.hibernate.FlushMode; import org.hibernate.JDBCException; import org.hibernate.Session; +import org.hibernate.StatelessSession; import org.hibernate.engine.spi.SessionImplementor; import org.jspecify.annotations.Nullable; @@ -39,10 +42,12 @@ import org.springframework.transaction.InvalidIsolationLevelException; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.support.ResourceTransactionDefinition; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; /** * {@link org.springframework.orm.jpa.JpaDialect} implementation for Hibernate. - * Compatible with Hibernate ORM 7.x. + * Compatible with Hibernate ORM 7.x and 8.x. * * @author Juergen Hoeller * @author Costin Leau @@ -54,6 +59,10 @@ import org.springframework.transaction.support.ResourceTransactionDefinition; @SuppressWarnings("serial") public class HibernateJpaDialect extends DefaultJpaDialect { + /** Hibernate 8.0: inherited setProperty(String, Object) method from JPA 4.0 EntityAgent. */ + private static final @Nullable Method STATELESS_SESSION_SET_PROPERTY = ClassUtils.getMethodIfAvailable( + StatelessSession.class, "setProperty", String.class, Object.class); + private final HibernateExceptionTranslator exceptionTranslator = new HibernateExceptionTranslator(); boolean prepareConnection = true; @@ -194,6 +203,21 @@ public class HibernateJpaDialect extends DefaultJpaDialect { return new HibernateConnectionHandle(entityManager.unwrap(SessionImplementor.class)); } + @Override + public StatelessSession deriveEntityAgent(EntityManager entityManager, @Nullable Map properties) + throws PersistenceException { + + StatelessSession entityAgent = entityManager.unwrap(Session.class).statelessWithOptions().connection().open(); + if (properties != null && STATELESS_SESSION_SET_PROPERTY != null) { + for (Map.Entry entry : properties.entrySet()) { + if (entry.getKey() instanceof String key) { + ReflectionUtils.invokeMethod(STATELESS_SESSION_SET_PROPERTY, entityAgent, key, entry.getValue()); + } + } + } + return entityAgent; + } + @Override public @Nullable DataAccessException translateExceptionIfPossible(RuntimeException ex) { return this.exceptionTranslator.translateExceptionIfPossible(ex); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java index b4e31f3d2f7..26da37be04b 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java @@ -50,7 +50,7 @@ import org.springframework.transaction.jta.JtaTransactionManager; /** * {@link org.springframework.orm.jpa.JpaVendorAdapter} implementation for Hibernate. - * Compatible with Hibernate ORM 7.x. + * Compatible with Hibernate ORM 7.x and 8.x. * *

Exposes Hibernate's persistence provider and Hibernate's Session as extended * EntityManager interface, and adapts {@link AbstractJpaVendorAdapter}'s common