Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nexdrew/6992329 to your computer and use it in GitHub Desktop.
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.
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();
}
}
};
}
}
@nexdrew
Copy link
Author

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