Browse Source

Add transactional support for StatelessSession (next to regular Session)

Exposes JPA-style shared proxy instances through LocalSessionFactoryBean.

Closes gh-7184
pull/34146/merge
Juergen Hoeller 5 months ago
parent
commit
03a8933f58
  1. 6
      spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java
  2. 7
      spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java
  3. 39
      spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java
  4. 30
      spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/LocalSessionFactoryBean.java
  5. 38
      spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java
  6. 158
      spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SharedSessionCreator.java
  7. 143
      spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionContext.java
  8. 9
      spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionSynchronization.java
  9. 67
      spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java

6
spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java

@ -37,7 +37,7 @@ import org.springframework.util.Assert; @@ -37,7 +37,7 @@ import org.springframework.util.Assert;
*/
public class EntityManagerHolder extends ResourceHolderSupport {
private final @Nullable EntityManager entityManager;
protected @Nullable EntityManager entityManager;
private boolean transactionActive;
@ -78,4 +78,8 @@ public class EntityManagerHolder extends ResourceHolderSupport { @@ -78,4 +78,8 @@ public class EntityManagerHolder extends ResourceHolderSupport {
this.savepointManager = null;
}
protected void closeAll() {
EntityManagerFactoryUtils.closeEntityManager(this.entityManager);
}
}

7
spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java

@ -640,11 +640,8 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager @@ -640,11 +640,8 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager
// Remove the entity manager holder from the thread.
if (txObject.isNewEntityManagerHolder()) {
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
if (logger.isDebugEnabled()) {
logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
}
EntityManagerFactoryUtils.closeEntityManager(em);
logger.debug("Closing JPA EntityManager after transaction");
txObject.getEntityManagerHolder().closeAll();
}
else {
logger.debug("Not closing pre-bound JPA EntityManager after transaction");

39
spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java

@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
package org.springframework.orm.jpa.hibernate;
import java.sql.Connection;
import java.util.Map;
import java.util.function.Consumer;
import javax.sql.DataSource;
@ -29,12 +28,8 @@ import org.hibernate.Interceptor; @@ -29,12 +28,8 @@ import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.hibernate.service.UnknownServiceException;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
@ -372,7 +367,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana @@ -372,7 +367,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
// Check for SessionFactory's DataSource.
if (this.autodetectDataSource && getDataSource() == null) {
DataSource sfds = determineDataSource();
DataSource sfds = SpringSessionContext.determineDataSource(obtainSessionFactory());
if (sfds != null) {
// Use the SessionFactory's DataSource for exposing transactions to JDBC code.
if (logger.isDebugEnabled()) {
@ -384,36 +379,6 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana @@ -384,36 +379,6 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
}
/**
* Determine the DataSource of the given SessionFactory.
* @return the DataSource, or {@code null} if none found
* @see ConnectionProvider
*/
protected @Nullable DataSource determineDataSource() {
SessionFactory sessionFactory = obtainSessionFactory();
Map<String, Object> props = sessionFactory.getProperties();
if (props != null) {
Object dataSourceValue = props.get(Environment.JAKARTA_NON_JTA_DATASOURCE);
if (dataSourceValue instanceof DataSource dataSourceToUse) {
return dataSourceToUse;
}
}
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;
}
@Override
public Object getResourceFactory() {
@ -735,7 +700,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana @@ -735,7 +700,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
if (logger.isDebugEnabled()) {
logger.debug("Closing Hibernate Session [" + session + "] after transaction");
}
EntityManagerFactoryUtils.closeEntityManager(session);
txObject.getSessionHolder().closeAll();
}
else {
if (logger.isDebugEnabled()) {

30
spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/LocalSessionFactoryBean.java

@ -23,7 +23,9 @@ import java.util.Properties; @@ -23,7 +23,9 @@ import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
@ -41,6 +43,7 @@ import org.springframework.beans.factory.BeanFactoryAware; @@ -41,6 +43,7 @@ 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.SmartFactoryBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ResourceLoaderAware;
@ -77,7 +80,7 @@ import org.springframework.core.type.filter.TypeFilter; @@ -77,7 +80,7 @@ import org.springframework.core.type.filter.TypeFilter;
* @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
*/
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
implements FactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware,
implements SmartFactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware,
InitializingBean, SmartInitializingSingleton, DisposableBean {
private @Nullable DataSource dataSource;
@ -134,6 +137,10 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator @@ -134,6 +137,10 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
private @Nullable SessionFactory sessionFactory;
private @Nullable Session sharedSession;
private @Nullable StatelessSession sharedStatelessSession;
/**
* Set the DataSource to be used by the SessionFactory.
@ -565,6 +572,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator @@ -565,6 +572,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
// Build SessionFactory instance.
this.configuration = sfb;
this.sessionFactory = buildSessionFactory(sfb);
this.sharedSession = SharedSessionCreator.createSharedSession(this.sessionFactory);
this.sharedStatelessSession = SharedSessionCreator.createSharedStatelessSession(this.sessionFactory);
}
@Override
@ -614,9 +623,24 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator @@ -614,9 +623,24 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
}
/**
* Return either the singleton SessionFactory or a shared (Stateless)Session proxy.
*/
@Override
public <S> @Nullable S getObject(Class<S> type) throws Exception {
if (Session.class.isAssignableFrom(type)) {
return type.cast(this.sharedSession);
}
if (StatelessSession.class.isAssignableFrom(type)) {
return type.cast(this.sharedStatelessSession);
}
return SmartFactoryBean.super.getObject(type);
}
@Override
public boolean isSingleton() {
return true;
public boolean supportsType(Class<?> type) {
return (type == Session.class || type == StatelessSession.class ||
SmartFactoryBean.super.supportsType(type));
}

38
spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java

@ -18,10 +18,12 @@ package org.springframework.orm.jpa.hibernate; @@ -18,10 +18,12 @@ package org.springframework.orm.jpa.hibernate;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.StatelessSession;
import org.hibernate.Transaction;
import org.jspecify.annotations.Nullable;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.util.Assert;
/**
* Resource holder wrapping a Hibernate {@link Session} (plus an optional {@link Transaction}).
@ -37,6 +39,8 @@ import org.springframework.orm.jpa.EntityManagerHolder; @@ -37,6 +39,8 @@ import org.springframework.orm.jpa.EntityManagerHolder;
*/
class SessionHolder extends EntityManagerHolder {
private @Nullable StatelessSession statelessSession;
private @Nullable Transaction transaction;
private @Nullable FlushMode previousFlushMode;
@ -46,11 +50,37 @@ class SessionHolder extends EntityManagerHolder { @@ -46,11 +50,37 @@ class SessionHolder extends EntityManagerHolder {
super(session);
}
public SessionHolder(StatelessSession session) {
super(null);
this.statelessSession = session;
}
public void setSession(Session session) {
this.entityManager = session;
}
public Session getSession() {
return (Session) getEntityManager();
}
public boolean hasSession() {
return (this.entityManager != null);
}
public void setStatelessSession(StatelessSession statelessSession) {
this.statelessSession = statelessSession;
}
public StatelessSession getStatelessSession() {
Assert.state(this.statelessSession != null, "No StatelessSession available");
return this.statelessSession;
}
public boolean hasStatelessSession() {
return (this.statelessSession != null);
}
public void setTransaction(@Nullable Transaction transaction) {
this.transaction = transaction;
setTransactionActive(transaction != null);
@ -76,4 +106,12 @@ class SessionHolder extends EntityManagerHolder { @@ -76,4 +106,12 @@ class SessionHolder extends EntityManagerHolder {
this.previousFlushMode = null;
}
@Override
protected void closeAll() {
super.closeAll();
if (this.statelessSession != null && this.statelessSession.isOpen()) {
this.statelessSession.close();
}
}
}

158
spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SharedSessionCreator.java

@ -0,0 +1,158 @@ @@ -0,0 +1,158 @@
/*
* 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.springframework.orm.jpa.hibernate;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.function.Supplier;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.jspecify.annotations.Nullable;
/**
* Delegate for creating shareable {@link Session}/{@link StatelessSession}
* references for a given {@link SessionFactory}.
*
* <p>Typically used next to {@link LocalSessionFactoryBuilder}. Note that
* {@link LocalSessionFactoryBean} exposes shared {@link Session} as well
* as {@link StatelessSession} references for dependency injection already,
* avoiding the need to define separate beans for the shared sessions.
*
* @author Juergen Hoeller
* @since 7.0
* @see LocalSessionFactoryBuilder
* @see LocalSessionFactoryBean
* @see org.springframework.orm.jpa.SharedEntityManagerCreator
*/
public abstract class SharedSessionCreator {
/**
* Create a shared {@link Session} proxy for the given {@link SessionFactory}.
* <p>The returned instance behaves like {@link SessionFactory#getCurrentSession()}
* but without the manual get call, automatically delegating every {@link Session}
* method invocation to the current thread-bound transactional session instance.
* Designed to work with {@link HibernateTransactionManager} as well as JTA.
* <p>Alternatively, use {@link SessionFactory#getCurrentSession()} directly.
* @param sessionFactory the SessionFactory to build the Session proxy for
* @see SessionFactory#getCurrentSession()
*/
public static Session createSharedSession(SessionFactory sessionFactory) {
return (Session) Proxy.newProxyInstance(SharedSessionCreator.class.getClassLoader(),
new Class<?>[] {Session.class},
new SharedSessionInvocationHandler(sessionFactory, sessionFactory::getCurrentSession));
}
/**
* Create a shared {@link StatelessSession} proxy for the given {@link SessionFactory}.
* <p>The returned instance automatically delegates every {@link StatelessSession}
* method invocation to the current thread-bound transactional session instance.
* On the first invocation within a new transaction, a {@link StatelessSession}
* will be opened for the current transactional JDBC Connection.
* <p>Works with {@link HibernateTransactionManager} (side by side with a
* thread-bound regular Session that drives the transaction) as well as
* {@link org.springframework.jdbc.support.JdbcTransactionManager} or
* {@link org.springframework.transaction.jta.JtaTransactionManager}
* (with a plain StatelessSession on top of a transactional JDBC Connection).
* <p>Alternatively, call {@link SpringSessionContext#currentStatelessSession}
* for every operation, avoiding the need for a proxy.
* @param sessionFactory the SessionFactory to build the StatelessSession proxy for
* @see SpringSessionContext#currentStatelessSession(SessionFactory)
*/
public static StatelessSession createSharedStatelessSession(SessionFactory sessionFactory) {
return (StatelessSession) Proxy.newProxyInstance(SharedSessionCreator.class.getClassLoader(),
new Class<?>[] {StatelessSession.class},
new SharedSessionInvocationHandler(sessionFactory,
() -> SpringSessionContext.currentStatelessSession(sessionFactory)));
}
private static class SharedSessionInvocationHandler implements InvocationHandler {
private final SessionFactory sessionFactory;
private final Supplier<Object> currentSessionSupplier;
public SharedSessionInvocationHandler(SessionFactory sessionFactory, Supplier<Object> currentSessionSupplier) {
this.sessionFactory = sessionFactory;
this.currentSessionSupplier = currentSessionSupplier;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "equals" -> {
// Only consider equal when proxies are identical.
return (proxy == args[0]);
}
case "hashCode" -> {
// Use hashCode of EntityManager proxy.
return hashCode();
}
case "toString" -> {
// Deliver toString without touching a target EntityManager.
return "Shared Session proxy for target factory [" + this.sessionFactory + "]";
}
case "getSessionFactory", "getEntityManagerFactory" -> {
// JPA 2.0: return EntityManagerFactory without creating an EntityManager.
return this.sessionFactory;
}
case "getCriteriaBuilder", "getMetamodel" -> {
// JPA 2.0: return EntityManagerFactory's CriteriaBuilder/Metamodel (avoid creation of EntityManager)
try {
return SessionFactory.class.getMethod(method.getName()).invoke(this.sessionFactory);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
case "unwrap" -> {
// JPA 2.0: handle unwrap method - could be a proxy match.
Class<?> targetClass = (Class<?>) args[0];
if (targetClass != null && targetClass.isInstance(proxy)) {
return proxy;
}
}
case "isOpen" -> {
// Handle isOpen method: always return true.
return true;
}
case "close" -> {
// Handle close method: suppress, not valid.
return null;
}
case "getTransaction" -> {
throw new IllegalStateException(
"Not allowed to create transaction on shared EntityManager - " +
"use Spring transactions or EJB CMT instead");
}
}
Object target = this.currentSessionSupplier.get();
try {
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
}

143
spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionContext.java

@ -16,6 +16,11 @@ @@ -16,6 +16,11 @@
package org.springframework.orm.jpa.hibernate;
import java.sql.Connection;
import java.util.Map;
import javax.sql.DataSource;
import jakarta.transaction.Status;
import jakarta.transaction.SystemException;
import jakarta.transaction.TransactionManager;
@ -23,11 +28,18 @@ import org.apache.commons.logging.LogFactory; @@ -23,11 +28,18 @@ import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.cfg.Environment;
import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.service.UnknownServiceException;
import org.jspecify.annotations.Nullable;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@ -78,27 +90,31 @@ public class SpringSessionContext implements CurrentSessionContext { @@ -78,27 +90,31 @@ public class SpringSessionContext implements CurrentSessionContext {
@Override
public Session currentSession() throws HibernateException {
Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
SessionHolder holder = null;
if (value instanceof Session session) {
return session;
}
else if (value instanceof SessionHolder sessionHolder) {
// HibernateTransactionManager
Session session = sessionHolder.getSession();
if (!sessionHolder.isSynchronizedWithTransaction() &&
TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
sessionHolder.setSynchronizedWithTransaction(true);
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
FlushMode flushMode = session.getHibernateFlushMode();
if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setHibernateFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode);
if (sessionHolder.hasSession()) {
Session session = sessionHolder.getSession();
if (!sessionHolder.isSynchronizedWithTransaction() &&
TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
sessionHolder.setSynchronizedWithTransaction(true);
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
FlushMode flushMode = session.getHibernateFlushMode();
if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setHibernateFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode);
}
}
return session;
}
return session;
holder = sessionHolder;
}
else if (value instanceof EntityManagerHolder entityManagerHolder) {
// JpaTransactionManager
@ -122,15 +138,25 @@ public class SpringSessionContext implements CurrentSessionContext { @@ -122,15 +138,25 @@ public class SpringSessionContext implements CurrentSessionContext {
}
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
Session session;
DataSource dataSource = determineDataSource(this.sessionFactory);
if (dataSource != null) {
session = this.sessionFactory.withOptions()
.connection(DataSourceUtils.getConnection(dataSource))
.openSession();
}
else {
session = this.sessionFactory.openSession();
}
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setHibernateFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
sessionHolder.setSynchronizedWithTransaction(true);
if (holder != null) {
holder.setSession(session);
}
else {
bindSessionHolder(this.sessionFactory, new SessionHolder(session));
}
return session;
}
else {
@ -138,4 +164,81 @@ public class SpringSessionContext implements CurrentSessionContext { @@ -138,4 +164,81 @@ public class SpringSessionContext implements CurrentSessionContext {
}
}
/**
* Obtain a {@link StatelessSession} for the current transaction.
* @param sessionFactory the target SessionFactory
* @return the current StatelessSession
*/
public static StatelessSession currentStatelessSession(SessionFactory sessionFactory) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
}
Object value = TransactionSynchronizationManager.getResource(sessionFactory);
if (value instanceof StatelessSession statelessSession) {
return statelessSession;
}
SessionHolder holder = null;
if (value instanceof SessionHolder sessionHolder) {
if (sessionHolder.hasStatelessSession()) {
return sessionHolder.getStatelessSession();
}
holder = sessionHolder;
}
StatelessSession session = sessionFactory.openStatelessSession(determineConnection(sessionFactory, holder));
if (holder != null) {
holder.setStatelessSession(session);
}
else {
bindSessionHolder(sessionFactory, new SessionHolder(session));
}
return session;
}
private static void bindSessionHolder(SessionFactory sessionFactory, SessionHolder holder) {
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(holder, sessionFactory, true));
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
holder.setSynchronizedWithTransaction(true);
}
private static Connection determineConnection(SessionFactory sessionFactory, @Nullable SessionHolder holder) {
if (holder != null && holder.getSession() instanceof SessionImplementor session) {
return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection();
}
DataSource dataSource = determineDataSource(sessionFactory);
if (dataSource != null) {
return DataSourceUtils.getConnection(dataSource);
}
throw new IllegalStateException(
"Cannot determine JDBC DataSource for Hibernate SessionFactory: " + sessionFactory);
}
/**
* Determine the DataSource of the given SessionFactory.
* @return the DataSource, or {@code null} if none found
* @see ConnectionProvider
*/
static @Nullable DataSource determineDataSource(SessionFactory sessionFactory) {
Map<String, Object> props = sessionFactory.getProperties();
if (props != null) {
Object dataSourceValue = props.get(Environment.JAKARTA_NON_JTA_DATASOURCE);
if (dataSourceValue instanceof DataSource dataSourceToUse) {
return dataSourceToUse;
}
}
if (sessionFactory instanceof SessionFactoryImplementor sfi) {
try {
ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class);
if (cp != null) {
return cp.unwrap(DataSource.class);
}
}
catch (UnknownServiceException ex) {
// Ignore - cannot determine
}
}
return null;
}
}

9
spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SpringSessionSynchronization.java

@ -25,7 +25,6 @@ import org.springframework.core.Ordered; @@ -25,7 +25,6 @@ import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@ -44,7 +43,7 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere @@ -44,7 +43,7 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
* to execute Session cleanup before JDBC Connection cleanup, if any.
* @see DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
*/
private static final int SESSION_SYNCHRONIZATION_ORDER =
static final int SESSION_SYNCHRONIZATION_ORDER =
DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
private final SessionHolder sessionHolder;
@ -56,10 +55,6 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere @@ -56,10 +55,6 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
private boolean holderActive = true;
public SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory) {
this(sessionHolder, sessionFactory, false);
}
public SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory, boolean newSession) {
this.sessionHolder = sessionHolder;
this.sessionFactory = sessionFactory;
@ -162,7 +157,7 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere @@ -162,7 +157,7 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
this.sessionHolder.setSynchronizedWithTransaction(false);
// Call close() at this point if it's a new Session...
if (this.newSession) {
EntityManagerFactoryUtils.closeEntityManager(this.sessionHolder.getSession());
this.sessionHolder.closeAll();
}
}
}

67
spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java

@ -18,16 +18,22 @@ package org.springframework.orm.jpa.hibernate; @@ -18,16 +18,22 @@ package org.springframework.orm.jpa.hibernate;
import java.util.List;
import javax.sql.DataSource;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.query.Query;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
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.domain.Person;
import org.springframework.transaction.support.TransactionTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@ -42,6 +48,15 @@ class HibernateNativeEntityManagerFactoryIntegrationTests extends AbstractContai @@ -42,6 +48,15 @@ class HibernateNativeEntityManagerFactoryIntegrationTests extends AbstractContai
@Autowired
private SessionFactory sessionFactory;
@Autowired
private Session sharedSession;
@Autowired
private StatelessSession statelessSession;
@Autowired
private DataSource dataSource;
@Autowired
private ApplicationContext applicationContext;
@ -83,6 +98,58 @@ class HibernateNativeEntityManagerFactoryIntegrationTests extends AbstractContai @@ -83,6 +98,58 @@ class HibernateNativeEntityManagerFactoryIntegrationTests extends AbstractContai
assertThat(q.getResultList().get(0).postLoaded).isSameAs(applicationContext);
}
@Test
public void testSharedSession() {
String firstName = "Tony";
insertPerson(firstName);
Query<Person> q = sharedSession.createQuery("select p from Person as p", Person.class);
assertThat(q.getResultList()).hasSize(1);
assertThat(q.getResultList().get(0).getFirstName()).isEqualTo(firstName);
assertThat(q.getResultList().get(0).postLoaded).isSameAs(applicationContext);
endTransaction();
DataSourceTransactionManager dstm = new DataSourceTransactionManager(dataSource);
new TransactionTemplate(dstm).execute(status -> {
insertPerson(firstName);
Query<Person> q2 = sharedSession.createQuery("select p from Person as p", Person.class);
assertThat(q2.getResultList()).hasSize(1);
assertThat(q2.getResultList().get(0).getFirstName()).isEqualTo(firstName);
assertThat(q2.getResultList().get(0).postLoaded).isSameAs(applicationContext);
Query<Person> q3 = statelessSession.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
public void testStatelessSession() {
String firstName = "Tony";
insertPerson(firstName);
Query<Person> 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();
DataSourceTransactionManager dstm = new DataSourceTransactionManager(dataSource);
new TransactionTemplate(dstm).execute(status -> {
insertPerson(firstName);
Query<Person> 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<Person> 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 // SPR-16956
public void testReadOnly() {
assertThat(sessionFactory.getCurrentSession().getHibernateFlushMode()).isSameAs(FlushMode.AUTO);

Loading…
Cancel
Save