Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Modified fork of seykron's original workaround for SPR-9020. See comments for more details.
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();
}
}
};
}
}
Owner

nexdrew commented Oct 15, 2013

Use this implementation of Hibernate's CurrentSessionContext for compatibility with Propagation.SUPPORTS transaction definitions using Spring, JTA, and Hibernate 4.

For more info, see SPR-9020.

Changes from seykron's original version:

  • opened methods for extension (private -> protected)
  • extends ManagedSessionContext (only used when Spring delegation fails) in order to remove self-managed SessionFactoryImplementor and ManagedSessionContext instance variables
  • wrapped ManagedSessionContext logic to handle expected exceptions
  • prefers method-local SessionFactoryImplementor references
  • added logging via SLF4J
  • removed commons-lang dependency
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment