Forked from seykron/TransactionAwareSessionContext.java
Last active
December 25, 2015 14:39
-
-
Save nexdrew/6992329 to your computer and use it in GitHub Desktop.
Modified fork of seykron's original workaround for SPR-9020. See comments for more details.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Arrays; | |
import org.hibernate.FlushMode; | |
import org.hibernate.HibernateException; | |
import org.hibernate.Session; | |
import org.hibernate.context.TenantIdentifierMismatchException; | |
import org.hibernate.context.internal.ManagedSessionContext; | |
import org.hibernate.engine.spi.SessionFactoryImplementor; | |
import org.hibernate.service.jta.platform.spi.JtaPlatform; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.orm.hibernate4.SpringSessionContext; | |
import org.springframework.transaction.jta.JtaAfterCompletionSynchronization; | |
import org.springframework.transaction.support.TransactionSynchronization; | |
import org.springframework.transaction.support.TransactionSynchronizationAdapter; | |
import org.springframework.transaction.support.TransactionSynchronizationManager; | |
/** Session context that determines whether there exist a transaction in | |
* progress in the current thread, and if not, it opens the {@link Session}. | |
* <p> | |
* It delegates to {@link SpringSessionContext} to check whether there exist | |
* a Session or not. If it doesn't, the Session is created bound to the current | |
* thread via {@link ManagedSessionContext}. | |
* </p> | |
* <p> | |
* Sessions created by this context are registered in the available transaction | |
* synchronization strategy in order to cleanup and properly close Sessions | |
* when transactions finish. If there's no synchronization strategy available, | |
* the session will never be closed. | |
* </p> | |
* <p> | |
* The main reason to use this context instead of Spring's {@link SpringSessionContext} | |
* or Hibernate's {@link ManagedSessionContext} is for backwards compatibility with the | |
* {@link org.springframework.transaction.annotation.Propagation#SUPPORTS Propagation.SUPPORTS} | |
* transaction definition when using Spring-managed JTA transactions with Hibernate. | |
* </p> | |
* <p> | |
* For more information on why this is necessary, see | |
* <a href="https://jira.springsource.org/browse/SPR-9020">SPR-9020</a> | |
* </p> | |
* @author Matias Mirabelli | |
* @author Andrew Goode | |
*/ | |
public class TransactionAwareSessionContext extends ManagedSessionContext { | |
/** ID for serialization. */ | |
private static final long serialVersionUID = 312979308153913432L; | |
/** SLF4J Logger */ | |
private static final Logger logger = LoggerFactory.getLogger(TransactionAwareSessionContext.class); | |
/** Default session context to use before creating a new session; it's never null. */ | |
protected final SpringSessionContext defaultSessionContext; | |
/** Creates a new session context and sets the related session factory. | |
* | |
* @param factory Context session factory. Should not be null. | |
*/ | |
public TransactionAwareSessionContext(SessionFactoryImplementor factory) { | |
super(factory); | |
this.defaultSessionContext = new SpringSessionContext(factory); | |
} | |
/** Binds the configured session to Spring's transaction manager strategy | |
* if there's no session. | |
* | |
* @return Returns the configured session, or the one managed by Spring. | |
* Never returns null. | |
*/ | |
public Session currentSession() throws HibernateException { | |
Session session; | |
boolean trace = logger.isTraceEnabled(); | |
try { | |
// first try default Spring/Hibernate4 JTA logic | |
session = defaultSessionContext.currentSession(); | |
if(trace) logger.trace("Using Hibernate Session bound to JTA transaction"); | |
return session; | |
} catch(HibernateException propagationBehaviorNotSupported) { | |
// expecting "Unable to locate current JTA transaction" at org.hibernate.context.internal.JTASessionContext.currentSession(JTASessionContext.java:88) | |
// next try super logic (in case Session already opened and bound to thread) | |
try { | |
session = super.currentSession(); | |
if(trace) logger.trace("Using Hibernate Session previously bound to thread via ManagedSessionContext"); | |
return session; | |
} catch(TenantIdentifierMismatchException unexpected) { | |
logger.error("Rethrowing unexpected TenantIdentifierMismatchException", unexpected); | |
throw unexpected; | |
} catch(HibernateException expected) {} | |
// last try opening Session and bind it to thread | |
SessionFactoryImplementor factory = this.factory(); | |
String msg = trace ? "Using newly opened Hibernate Session" : null; | |
session = factory.openSession(); | |
if(registerSynchronization(factory, session)) { | |
// AUTO flush mode required for synchronization | |
FlushMode mode = session.getFlushMode(); | |
if(FlushMode.isManualFlushMode(mode) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { | |
if(trace) msg += ", flush mode reset from "+mode+" to AUTO"; | |
session.setFlushMode(FlushMode.AUTO); | |
} | |
} | |
bind(session); // unconditional bind to avoid opening multiple sessions | |
if(trace) logger.trace(msg); | |
return session; | |
} | |
} | |
/** Registers transaction synchronization with session in order to clean | |
* up and close the session when transaction finishes. | |
* | |
* @param factory The factory used to open the given session. | |
* @param session Session to register into transaction synchronization. | |
* Cannot be null. | |
* @return Returns <code>true</code> if the session was register into any | |
* available synchronization strategy, <code>false</code> otherwise. | |
*/ | |
protected boolean registerSynchronization(final SessionFactoryImplementor factory, final Session session) { | |
// try Spring first | |
if(TransactionSynchronizationManager.isSynchronizationActive()) { | |
TransactionSynchronizationManager.registerSynchronization(createTransactionSynchronization(factory, session)); | |
if(logger.isTraceEnabled()) logger.trace("Hibernate Session synchronization registered with Spring"); | |
return true; | |
} else { | |
// try JTA next | |
JtaPlatform jtaPlatform = factory.getServiceRegistry().getService(JtaPlatform.class); | |
if(jtaPlatform != null && jtaPlatform.canRegisterSynchronization()) { | |
jtaPlatform.registerSynchronization(new JtaAfterCompletionSynchronization(Arrays.asList(createTransactionSynchronization(factory, session)))); | |
if(logger.isTraceEnabled()) logger.trace("Hibernate Session synchronization registered with JTA platform"); | |
return true; | |
} | |
} | |
if(logger.isTraceEnabled()) logger.trace("Hibernate Session synchronization NOT registered"); | |
return false; | |
} | |
/** Creates a transaction synchronization object for the specified session. | |
* | |
* @param factory The factory used to open the given session - will be used to | |
* unbind the session after transaction completion. | |
* @param session Session to synchronize using the created object. Cannot be | |
* null. | |
* @return A valid transaction synchronization. Never returns null. | |
*/ | |
protected TransactionSynchronization createTransactionSynchronization(final SessionFactoryImplementor factory, final Session session) { | |
return new TransactionSynchronizationAdapter() { | |
@Override | |
public void afterCompletion(int status) { | |
// unconditional unbind and close | |
Session unbound = unbind(factory); | |
if(unbound != null) { | |
if(logger.isTraceEnabled()) logger.trace("Closing previously bound Hibernate Session"); | |
unbound.close(); | |
} | |
if(unbound != session) { | |
// certainly an odd scenario | |
logger.warn("Hibernate Session bound to thread DOES NOT MATCH previously opened one, closing both"); | |
session.close(); | |
} | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use this implementation of Hibernate's
CurrentSessionContext
for compatibility withPropagation.SUPPORTS
transaction definitions using Spring, JTA, and Hibernate 4.For more info, see SPR-9020.
Changes from seykron's original version:
ManagedSessionContext
(only used when Spring delegation fails) in order to remove self-managedSessionFactoryImplementor
andManagedSessionContext
instance variablesManagedSessionContext
logic to handle expected exceptionsSessionFactoryImplementor
references