* <p>
* This behaviour essentially revolves around the resources map in the {@link org.springframework.transaction.support.TransactionSynchronizationManager TransactionSynchronizationManager}.
* This resources map contains two entries of interest,
- * - (Autowired) {@link org.hibernate.SessionFactory} mapped to the {@link org.springframework.orm.hibernate5.SessionHolder}
- * - (Autowired) {@link javax.sql.DataSource} mapped to the {@link org.springframework.jdbc.datasource.ConnectionHolder}
- * <p>
+ * <ul>
+ * <li>(Autowired) {@link org.hibernate.SessionFactory} mapped to the {@link org.springframework.orm.hibernate5.SessionHolder}</li>
+ * <li>(Autowired) {@link javax.sql.DataSource} mapped to the {@link org.springframework.jdbc.datasource.ConnectionHolder}</li>
+ * </ul>
+ * <p>
* The SessionHolder object itself contains the {@link org.hibernate.Session Session} as well as the {@link org.hibernate.Transaction object.
* The ConnectionHolder contains the (JDBC) {@link java.sql.Connection Connection} to the database. For every action to do with the
* transaction object it is required to have both entries present in the resources. Both the session as well as the connection
* is in fact how hibernate implements the default 'session-per-request' pattern internally.
* <p>
* Given the above conditions, this class manages long running sessions by providing the following methods,
- * - {@link #bind()} : binds the session owned by this conversation to the resource map
- * - {@link #startTransaction()} : starts a transaction
- * - {@link #commit()} : commits the current transaction, with the option of restarting a new transaction.
- * - {@link #unbind()} : unbinds the session owned by this conversation from the resource map.
- * - {@link #close()} : closes the session owned by this conversation
+ * <ul>
+ * <li>{@link #bind()} : binds the session owned by this conversation to the resource map.</li>
+ * <li>{@link #startTransaction()} : starts a transaction.</li>
+ * <li>{@link #commit()} : commits the current transaction, with the option of restarting a new transaction.</li>
+ * <li>{@link #unbind()} : unbinds the session owned by this conversation from the resource map.</li>
+ * <li>{@link #close()} : closes the session owned by this conversation.</li>
+ * </ul>
* <p>
* With the exception of {@link #unbind()} (which should be called explicitly), the above sequence must be strictly followed to
* maintain a consistent session state. Even though it is possible to interweave multiple conversations at the same time, for a
* @see http://www.hibernate.org/42.html
*
* @author n.hoffmann,c.mathew
- * @created 12.03.2009
- * @version 1.0
+ * @since 12.03.2009
*/
public class ConversationHolder {
private boolean closed = false;
+ private FlushMode defaultFlushMode = FlushMode.COMMIT;
+
/**
* Simple constructor used by Spring only
*/
closed = false;
}
+ /**
+ * Create a new Conversation holder and bind it immediately.
+ *
+ * @param dataSource
+ * @param sessionFactory
+ * @param transactionManager
+ */
public ConversationHolder(DataSource dataSource, SessionFactory sessionFactory,
PlatformTransactionManager transactionManager) {
+ this(dataSource, sessionFactory, transactionManager, true);
+ }
+
+ /**
+ * Create a new Conversation holder and optionally bind it immediately.
+ *
+ * @param dataSource
+ * @param sessionFactory
+ * @param transactionManager
+ */
+ public ConversationHolder(DataSource dataSource, SessionFactory sessionFactory,
+ PlatformTransactionManager transactionManager, boolean bindNow) {
this();
this.dataSource = dataSource;
this.sessionFactory = sessionFactory;
this.transactionManager = transactionManager;
- bind();
-
- if(TransactionSynchronizationManager.hasResource(getDataSource())){
- TransactionSynchronizationManager.unbindResource(getDataSource());
+ if(bindNow) {
+ bind();
+ if(TransactionSynchronizationManager.hasResource(getDataSource())){
+ TransactionSynchronizationManager.unbindResource(getDataSource());
+ }
}
}
logger.info("Binding resources for ConversationHolder");
if(TransactionSynchronizationManager.isSynchronizationActive()){
+ logger.trace("Clearing active transaction synchronization");
TransactionSynchronizationManager.clearSynchronization();
}
if(TransactionSynchronizationManager.hasResource(getSessionFactory())){
+ logger.trace("Unbinding resource from TransactionSynchronizationManager with key: " + getSessionFactory());
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
- logger.info("Binding Session to TransactionSynchronizationManager: Session: " + getSessionHolder());
+ if(logger.isTraceEnabled()){
+ logger.trace("Binding Session to TransactionSynchronizationManager:" + getSessionHolder() + " Session [" + getSessionHolder().getSession().hashCode() + "] with key: " + getSessionFactory());
+ } else {
+ logger.info("Binding Session to TransactionSynchronizationManager: Session: " + getSessionHolder());
+ }
TransactionSynchronizationManager.bindResource(getSessionFactory(), getSessionHolder());
// unbind the current session.
// there is no need to bind a new session, since HibernateTransactionManager will create a new one
// if the resource map does not contain one (ditto for the datasource-to-connection entry).
+ if(logger.isTraceEnabled()){
+ logger.trace("Unbinding SessionFactory [" + getSessionFactory().hashCode() + "]");
+ }
TransactionSynchronizationManager.unbindResource(getSessionFactory());
if(TransactionSynchronizationManager.hasResource(getDataSource())){
+ if(logger.isTraceEnabled()){
+ logger.trace("Unbinding DataSource [" + getDataSource().hashCode() + "]");
+ }
TransactionSynchronizationManager.unbindResource(getDataSource());
}
}
public SessionHolder getSessionHolder(){
if(this.sessionHolder == null){
- logger.info("Creating new SessionHolder");
this.sessionHolder = new SessionHolder(getSession());
+ logger.info("Creating new SessionHolder:" + sessionHolder);
}
return this.sessionHolder;
}
public boolean isBound(){
//return sessionHolder != null && longSession != null && longSession.isConnected();
SessionHolder currentSessionHolder = (SessionHolder)TransactionSynchronizationManager.getResource(getSessionFactory());
- return longSession != null && currentSessionHolder != null && getSessionFactory().getCurrentSession() == longSession;
+ return longSession != null && currentSessionHolder != null && getSessionFactory().getCurrentSession().equals(longSession);
}
/**
* @return if there is a running transaction
*/
public boolean isTransactionActive(){
- return transactionStatus != null;
+ return transactionStatus != null && !transactionStatus.isCompleted();
}
- /* (non-Javadoc)
- * @see org.hibernate.Session#evict(java.lang.Object object)
- */
public void evict(Object object){
getSession().evict(object);
}
- /* (non-Javadoc)
- * @see org.hibernate.Session#refresh(java.lang.Object object)
- */
public void refresh(Object object){
getSession().refresh(object);
}
- /* (non-Javadoc)
- * @see org.hibernate.Session#clear()
- */
public void clear(){
getSession().clear();
}
* @return the session associated with this conversation manager
*/
public Session getSession() {
+
+ String whatStr;
+
if(longSession == null){
longSession = getNewSession();
+ whatStr = "Creating";
+ } else {
+ whatStr = "Reusing";
+ }
+ if(logger.isDebugEnabled()){
+ logger.debug(whatStr + " Session: [" + longSession.hashCode() + "] " + longSession);
+ } else {
+ logger.info(whatStr + " Session: [" + longSession.hashCode() + "] ");
}
return longSession;
}
// This will create a new session which must be explicitly managed by this conversation, which includes
// binding / unbinding / closing session as well as starting / committing transactions.
Session session = sessionFactory.openSession();
- session.setFlushMode(FlushMode.COMMIT);
- logger.info("Creating Session: [" + longSession + "]");
+ session.setFlushMode(getDefaultFlushMode());
+
return session;
}
return transactionStatus == null || transactionStatus.isCompleted();
}
+ /**
+ * @return the defaultFlushMode
+ */
+ public FlushMode getDefaultFlushMode() {
+ return defaultFlushMode;
+ }
+
+ /**
+ * @param defaultFlushMode the defaultFlushMode to set
+ */
+ public void setDefaultFlushMode(FlushMode defaultFlushMode) {
+ this.defaultFlushMode = defaultFlushMode;
+ }
+
}