org.beangle.commons.orm.hibernate.HibernateTransactionManager.java Source code

Java tutorial

Introduction

Here is the source code for org.beangle.commons.orm.hibernate.HibernateTransactionManager.java

Source

/* Copyright c 2005-2012.
 * Licensed under GNU  LESSER General Public License, Version 3.
 * http://www.gnu.org/licenses
 */
package org.beangle.commons.orm.hibernate;

import java.sql.Connection;

import javax.sql.DataSource;

import org.beangle.commons.orm.hibernate.internal.SessionHolder;
import org.beangle.commons.orm.hibernate.internal.SessionUtils;
import org.hibernate.*;
import org.hibernate.exception.GenericJDBCException;
import org.hibernate.impl.SessionImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * @author chaostone
 * @version $Id: HibernateTransactionManager.java Feb 28, 2012 10:32:50 PM chaostone $
 */
public class HibernateTransactionManager extends AbstractPlatformTransactionManager
        implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {

    private static final long serialVersionUID = 1L;

    private SessionFactory sessionFactory;

    private DataSource dataSource;

    private boolean autodetectDataSource = true;

    private boolean prepareConnection = true;

    private boolean hibernateManagedSession = false;

    private boolean earlyFlushBeforeCommit = false;

    private Object entityInterceptor;

    private SQLExceptionTranslator jdbcExceptionTranslator;

    private SQLExceptionTranslator defaultJdbcExceptionTranslator;

    /**
     * Just needed for entityInterceptorBeanName.
     * 
     * @see #setEntityInterceptorBeanName
     */
    private BeanFactory beanFactory;

    /**
     * Create a new HibernateTransactionManager instance.
     * A SessionFactory has to be set to be able to use it.
     * 
     * @see #setSessionFactory
     */
    public HibernateTransactionManager() {
    }

    /**
     * Create a new HibernateTransactionManager instance.
     * 
     * @param sessionFactory SessionFactory to manage transactions for
     */
    public HibernateTransactionManager(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        afterPropertiesSet();
    }

    /**
     * Set the SessionFactory that this instance should manage transactions for.
     */
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * Return the SessionFactory that this instance should manage transactions for.
     */
    public SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    /**
     * Set the JDBC DataSource that this instance should manage transactions for.
     * The DataSource should match the one used by the Hibernate SessionFactory:
     * for example, you could specify the same JNDI DataSource for both.
     * <p>
     * If the SessionFactory was configured with DataSourceConnectionProvider, i.e. by Spring's
     * SessionFactoryBean with a specified "dataSource", the DataSource will be auto-detected: You can
     * still explictly specify the DataSource, but you don't need to in this case.
     * <p>
     * A transactional JDBC Connection for this DataSource will be provided to application code
     * accessing this DataSource directly via DataSourceUtils or JdbcTemplate. The Connection will be
     * taken from the Hibernate Session.
     * <p>
     * The DataSource specified here should be the target DataSource to manage transactions for, not a
     * TransactionAwareDataSourceProxy. Only data access code may work with
     * TransactionAwareDataSourceProxy, while the transaction manager needs to work on the underlying
     * target DataSource. If there's nevertheless a TransactionAwareDataSourceProxy passed in, it will
     * be unwrapped to extract its target DataSource.
     * 
     * @see #setAutodetectDataSource
     * @see DataSourceConnectionProvider
     * @see SessionFactoryBean#setDataSource
     * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
     * @see org.springframework.jdbc.datasource.DataSourceUtils
     * @see org.springframework.jdbc.core.JdbcTemplate
     */
    public void setDataSource(DataSource dataSource) {
        if (dataSource instanceof TransactionAwareDataSourceProxy) {
            // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
            // for its underlying target DataSource, else data access code won't see
            // properly exposed transactions (i.e. transactions for the target DataSource).
            this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
        } else {
            this.dataSource = dataSource;
        }
    }

    /**
     * Return the JDBC DataSource that this instance manages transactions for.
     */
    public DataSource getDataSource() {
        return this.dataSource;
    }

    /**
     * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory,
     * if set via SessionFactoryBean's <code>setDataSource</code>. Default is "true".
     * <p>
     * Can be turned off to deliberately ignore an available DataSource, in order to not expose
     * Hibernate transactions as JDBC transactions for that DataSource.
     * 
     * @see #setDataSource
     * @see SessionFactoryBean#setDataSource
     */
    public void setAutodetectDataSource(boolean autodetectDataSource) {
        this.autodetectDataSource = autodetectDataSource;
    }

    /**
     * Set whether to prepare the underlying JDBC Connection of a transactional
     * Hibernate Session, that is, whether to apply a transaction-specific
     * isolation level and/or the transaction's read-only flag to the underlying
     * JDBC Connection.
     * <p>
     * Default is "true". If you turn this flag off, the transaction manager will not support
     * per-transaction isolation levels anymore. It will not call
     * <code>Connection.setReadOnly(true)</code> for read-only transactions anymore either. If this
     * flag is turned off, no cleanup of a JDBC Connection is required after a transaction, since no
     * Connection settings will get modified.
     * 
     * @see java.sql.Connection#setTransactionIsolation
     * @see java.sql.Connection#setReadOnly
     */
    public void setPrepareConnection(boolean prepareConnection) {
        this.prepareConnection = prepareConnection;
    }

    /**
     * Set whether to operate on a Hibernate-managed Session instead of a
     * Spring-managed Session, that is, whether to obtain the Session through
     * Hibernate's {@link org.hibernate.SessionFactory#getCurrentSession()} instead of
     * {@link org.hibernate.SessionFactory#openSession()} (with a Spring
     * {@link org.springframework.transaction.support.TransactionSynchronizationManager} check
     * preceding it).
     * <p>
     * Default is "false", i.e. using a Spring-managed Session: taking the current thread-bound
     * Session if available (e.g. in an Open-Session-in-View scenario), creating a new Session for the
     * current transaction otherwise.
     * <p>
     * Switch this flag to "true" in order to enforce use of a Hibernate-managed Session. Note that
     * this requires {@link org.hibernate.SessionFactory#getCurrentSession()} to always return a
     * proper Session when called for a Spring-managed transaction; transaction begin will fail if the
     * <code>getCurrentSession()</code> call fails.
     * <p>
     * This mode will typically be used in combination with a custom Hibernate
     * {@link org.hibernate.context.CurrentSessionContext} implementation that stores Sessions in a
     * place other than Spring's TransactionSynchronizationManager. It may also be used in combination
     * with Spring's Open-Session-in-View support (using Spring's default
     * {@link BeangleSessionContext}), in which case it subtly differs from the Spring-managed Session
     * mode: The pre-bound Session will <i>not</i> receive a <code>clear()</code> call (on rollback)
     * or a <code>disconnect()</code> call (on transaction completion) in such a scenario; this is
     * rather left up to a custom CurrentSessionContext implementation (if desired).
     */
    public void setHibernateManagedSession(boolean hibernateManagedSession) {
        this.hibernateManagedSession = hibernateManagedSession;
    }

    /**
     * Set whether to perform an early flush before proceeding with a commit.
     * <p>
     * Default is "false", performing an implicit flush as part of the actual commit step. Switch this
     * to "true" in order to enforce an explicit early flush right <i>before</i> the actual commit
     * step.
     * <p>
     * An early flush happens before the before-commit synchronization phase, making flushed state
     * visible to <code>beforeCommit</code> callbacks of registered
     * {@link org.springframework.transaction.support.TransactionSynchronization} objects. Such
     * explicit flush behavior is consistent with Spring-driven flushing in a JTA transaction
     * environment, so may also get enforced for consistency with JTA transaction behavior.
     * 
     * @see #prepareForCommit
     */
    public void setEarlyFlushBeforeCommit(boolean earlyFlushBeforeCommit) {
        this.earlyFlushBeforeCommit = earlyFlushBeforeCommit;
    }

    /**
     * Set the bean name of a Hibernate entity interceptor that allows to inspect
     * and change property values before writing to and reading from the database.
     * Will get applied to any new Session created by this transaction manager.
     * <p>
     * Requires the bean factory to be known, to be able to resolve the bean name to an interceptor
     * instance on session creation. Typically used for prototype interceptors, i.e. a new interceptor
     * instance per session.
     * <p>
     * Can also be used for shared interceptor instances, but it is recommended to set the interceptor
     * reference directly in such a scenario.
     * 
     * @param entityInterceptorBeanName the name of the entity interceptor in
     *          the bean factory
     * @see #setBeanFactory
     * @see #setEntityInterceptor
     */
    public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
        this.entityInterceptor = entityInterceptorBeanName;
    }

    /**
     * Set a Hibernate entity interceptor that allows to inspect and change
     * property values before writing to and reading from the database.
     * Will get applied to any new Session created by this transaction manager.
     * <p>
     * Such an interceptor can either be set at the SessionFactory level, i.e. on SessionFactoryBean,
     * or at the Session level, and HibernateTransactionManager. It's preferable to set it on
     * SessionFactoryBean or HibernateTransactionManager to avoid repeated configuration and guarantee
     * consistent behavior in transactions.
     * 
     * @see SessionFactoryBean#setEntityInterceptor
     */
    public void setEntityInterceptor(Interceptor entityInterceptor) {
        this.entityInterceptor = entityInterceptor;
    }

    /**
     * Return the current Hibernate entity interceptor, or <code>null</code> if none.
     * Resolves an entity interceptor bean name via the bean factory,
     * if necessary.
     * 
     * @throws IllegalStateException if bean name specified but no bean factory set
     * @throws BeansException if bean name resolution via the bean factory failed
     * @see #setEntityInterceptor
     * @see #setEntityInterceptorBeanName
     * @see #setBeanFactory
     */
    public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
        if (this.entityInterceptor instanceof Interceptor) {
            return (Interceptor) entityInterceptor;
        } else if (this.entityInterceptor instanceof String) {
            if (this.beanFactory == null) {
                throw new IllegalStateException(
                        "Cannot get entity interceptor via bean name if no bean factory set");
            }
            String beanName = (String) this.entityInterceptor;
            return this.beanFactory.getBean(beanName, Interceptor.class);
        } else {
            return null;
        }
    }

    /**
     * Set the JDBC exception translator for this transaction manager.
     * <p>
     * Applied to any SQLException root cause of a Hibernate JDBCException that is thrown on flush,
     * overriding Hibernate's default SQLException translation (which is based on Hibernate's Dialect
     * for a specific target database).
     * 
     * @param jdbcExceptionTranslator the exception translator
     * @see java.sql.SQLException
     * @see org.hibernate.JDBCException
     * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
     * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
     */
    public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
        this.jdbcExceptionTranslator = jdbcExceptionTranslator;
    }

    /**
     * Return the JDBC exception translator for this transaction manager, if any.
     */
    public SQLExceptionTranslator getJdbcExceptionTranslator() {
        return this.jdbcExceptionTranslator;
    }

    /**
     * The bean factory just needs to be known for resolving entity interceptor
     * bean names. It does not need to be set for any other mode of operation.
     * 
     * @see #setEntityInterceptorBeanName
     */
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public void afterPropertiesSet() {
        if (getSessionFactory() == null) {
            throw new IllegalArgumentException("Property 'sessionFactory' is required");
        }
        if (this.entityInterceptor instanceof String && this.beanFactory == null) {
            throw new IllegalArgumentException(
                    "Property 'beanFactory' is required for 'entityInterceptorBeanName'");
        }

        // Check for SessionFactory's DataSource.
        if (this.autodetectDataSource && getDataSource() == null) {
            DataSource sfds = SessionUtils.getDataSource(getSessionFactory());
            if (sfds != null) {
                // Use the SessionFactory's DataSource for exposing transactions to JDBC code.
                if (logger.isInfoEnabled()) {
                    logger.info("Using DataSource [" + sfds
                            + "] of Hibernate SessionFactory for HibernateTransactionManager");
                }
                setDataSource(sfds);
            }
        }
    }

    public Object getResourceFactory() {
        return getSessionFactory();
    }

    @Override
    protected Object doGetTransaction() {
        HibernateTransactionObject txObject = new HibernateTransactionObject();
        txObject.setSavepointAllowed(isNestedTransactionAllowed());

        SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
                .getResource(getSessionFactory());
        if (sessionHolder != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found thread-bound Session [" + SessionUtils.toString(sessionHolder.getSession())
                        + "] for Hibernate transaction");
            }
            txObject.setSessionHolder(sessionHolder);
        } else if (this.hibernateManagedSession) {
            try {
                Session session = getSessionFactory().getCurrentSession();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found Hibernate-managed Session [" + SessionUtils.toString(session)
                            + "] for Spring-managed transaction");
                }
                txObject.setExistingSession(session);
            } catch (HibernateException ex) {
                throw new DataAccessResourceFailureException(
                        "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
            }
        }

        if (getDataSource() != null) {
            ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager
                    .getResource(getDataSource());
            txObject.setConnectionHolder(conHolder);
        }

        return txObject;
    }

    @Override
    protected boolean isExistingTransaction(Object transaction) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
        return (txObject.hasSpringManagedTransaction()
                || (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction()));
    }

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;

        if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            throw new IllegalTransactionStateException(
                    "Pre-bound JDBC Connection found! HibernateTransactionManager does not support "
                            + "running within DataSourceTransactionManager if told to manage the DataSource itself. "
                            + "It is recommended to use a single HibernateTransactionManager for all transactions "
                            + "on a single DataSource, no matter whether Hibernate or JDBC access.");
        }

        Session session = null;

        try {
            if (txObject.getSessionHolder() == null
                    || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
                Interceptor entityInterceptor = getEntityInterceptor();
                Session newSession = (entityInterceptor != null ? getSessionFactory().openSession(entityInterceptor)
                        : getSessionFactory().openSession());
                if (logger.isDebugEnabled()) {
                    logger.debug("Opened new Session [" + SessionUtils.toString(newSession)
                            + "] for Hibernate transaction");
                }
                txObject.setSession(newSession);
            }

            session = txObject.getSessionHolder().getSession();

            if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
                // We're allowed to change the transaction settings of the JDBC Connection.
                if (logger.isDebugEnabled()) {
                    logger.debug("Preparing JDBC Connection of Hibernate Session [" + SessionUtils.toString(session)
                            + "]");
                }
                @SuppressWarnings("deprecation")
                Connection con = session.connection();
                Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
                txObject.setPreviousIsolationLevel(previousIsolationLevel);
            } else {
                // Not allowed to change the transaction settings of the JDBC Connection.
                if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
                    // We should set a specific isolation level but are not allowed to...
                    throw new InvalidIsolationLevelException(
                            "HibernateTransactionManager is not allowed to support custom isolation levels: "
                                    + "make sure that its 'prepareConnection' flag is on (the default) and that the "
                                    + "Hibernate connection release mode is set to 'on_close' (BeangleTransactionFactory's default). "
                                    + "Make sure that your SessionFactoryBean actually uses BeangleTransactionFactory: Your "
                                    + "Hibernate properties should *not* include a 'hibernate.transaction.factory_class' property!");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Not preparing JDBC Connection of Hibernate Session ["
                            + SessionUtils.toString(session) + "]");
                }
            }

            if (definition.isReadOnly() && txObject.isNewSession()) {
                // Just set to NEVER in case of a new Session for this transaction.
                session.setFlushMode(FlushMode.MANUAL);
            }

            if (!definition.isReadOnly() && !txObject.isNewSession()) {
                // We need AUTO or COMMIT for a non-read-only transaction.
                FlushMode flushMode = session.getFlushMode();
                if (flushMode.lessThan(FlushMode.COMMIT)) {
                    session.setFlushMode(FlushMode.AUTO);
                    txObject.getSessionHolder().setPreviousFlushMode(flushMode);
                }
            }

            Transaction hibTx;

            // Register transaction timeout.
            int timeout = determineTimeout(definition);
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
                // Applies to all statements, also to inserts, updates and deletes!
                hibTx = session.getTransaction();
                hibTx.setTimeout(timeout);
                hibTx.begin();
            } else {
                // Open a plain Hibernate transaction without specified timeout.
                hibTx = session.beginTransaction();
            }

            // Add the Hibernate transaction to the session holder.
            txObject.getSessionHolder().setTransaction(hibTx);

            // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
            if (getDataSource() != null) {
                @SuppressWarnings("deprecation")
                Connection con = session.connection();
                ConnectionHolder conHolder = new ConnectionHolder(con);
                if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                    conHolder.setTimeoutInSeconds(timeout);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]");
                }
                TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
                txObject.setConnectionHolder(conHolder);
            }

            // Bind the session holder to the thread.
            if (txObject.isNewSessionHolder()) {
                TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
            }
            txObject.getSessionHolder().setSynchronizedWithTransaction(true);
        }

        catch (Exception ex) {
            if (txObject.isNewSession()) {
                try {
                    if (session.getTransaction().isActive()) {
                        session.getTransaction().rollback();
                    }
                } catch (Throwable ex2) {
                    logger.debug("Could not rollback Session after failed transaction begin", ex);
                } finally {
                    SessionUtils.closeSession(session);
                }
            }
            throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
        }
    }

    @Override
    protected Object doSuspend(Object transaction) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
        txObject.setSessionHolder(null);
        SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
                .unbindResource(getSessionFactory());
        txObject.setConnectionHolder(null);
        ConnectionHolder connectionHolder = null;
        if (getDataSource() != null) {
            connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
        }
        return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
    }

    @Override
    protected void doResume(Object transaction, Object suspendedResources) {
        SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
        if (TransactionSynchronizationManager.hasResource(getSessionFactory())) {
            // From non-transactional code running in active transaction synchronization
            // -> can be safely removed, will be closed on transaction completion.
            TransactionSynchronizationManager.unbindResource(getSessionFactory());
        }
        TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder());
        if (getDataSource() != null) {
            TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
        }
    }

    @Override
    protected void prepareForCommit(DefaultTransactionStatus status) {
        if (this.earlyFlushBeforeCommit && status.isNewTransaction()) {
            HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
            Session session = txObject.getSessionHolder().getSession();
            if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) {
                logger.debug("Performing an early flush for Hibernate transaction");
                try {
                    session.flush();
                } catch (HibernateException ex) {
                    throw convertHibernateAccessException(ex);
                } finally {
                    session.setFlushMode(FlushMode.MANUAL);
                }
            }
        }
    }

    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
        if (status.isDebug()) {
            logger.debug("Committing Hibernate transaction on Session ["
                    + SessionUtils.toString(txObject.getSessionHolder().getSession()) + "]");
        }
        try {
            txObject.getSessionHolder().getTransaction().commit();
        } catch (org.hibernate.TransactionException ex) {
            // assumably from commit call to the underlying JDBC connection
            throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
        } catch (HibernateException ex) {
            // assumably failed to flush changes to database
            throw convertHibernateAccessException(ex);
        }
    }

    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
        if (status.isDebug()) {
            logger.debug("Rolling back Hibernate transaction on Session ["
                    + SessionUtils.toString(txObject.getSessionHolder().getSession()) + "]");
        }
        try {
            txObject.getSessionHolder().getTransaction().rollback();
        } catch (org.hibernate.TransactionException ex) {
            throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
        } catch (HibernateException ex) {
            // Shouldn't really happen, as a rollback doesn't cause a flush.
            throw convertHibernateAccessException(ex);
        } finally {
            if (!txObject.isNewSession() && !this.hibernateManagedSession) {
                // Clear all pending inserts/updates/deletes in the Session.
                // Necessary for pre-bound Sessions, to avoid inconsistent state.
                txObject.getSessionHolder().getSession().clear();
            }
        }
    }

    @Override
    protected void doSetRollbackOnly(DefaultTransactionStatus status) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
        if (status.isDebug()) {
            logger.debug("Setting Hibernate transaction on Session ["
                    + SessionUtils.toString(txObject.getSessionHolder().getSession()) + "] rollback-only");
        }
        txObject.setRollbackOnly();
    }

    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;

        // Remove the session holder from the thread.
        if (txObject.isNewSessionHolder()) {
            TransactionSynchronizationManager.unbindResource(getSessionFactory());
        }

        // Remove the JDBC connection holder from the thread, if exposed.
        if (getDataSource() != null) {
            TransactionSynchronizationManager.unbindResource(getDataSource());
        }

        Session session = txObject.getSessionHolder().getSession();
        if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) {
            // We're running with connection release mode "on_close": We're able to reset
            // the isolation level and/or read-only flag of the JDBC Connection here.
            // Else, we need to rely on the connection pool to perform proper cleanup.
            try {
                @SuppressWarnings("deprecation")
                Connection con = session.connection();
                DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
            } catch (HibernateException ex) {
                logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
            }
        }

        if (txObject.isNewSession()) {
            if (logger.isDebugEnabled()) {
                logger.debug(
                        "Closing Hibernate Session [" + SessionUtils.toString(session) + "] after transaction");
            }
            SessionUtils.closeSession(session);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Not closing pre-bound Hibernate Session [" + SessionUtils.toString(session)
                        + "] after transaction");
            }
            if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
                session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
            }
            if (!this.hibernateManagedSession) {
                session.disconnect();
            }
        }
        txObject.getSessionHolder().clear();
    }

    /**
     * Return whether the given Hibernate Session will always hold the same
     * JDBC Connection. This is used to check whether the transaction manager
     * can safely prepare and clean up the JDBC Connection used for a transaction.
     * <p>
     * Default implementation checks the Session's connection release mode to be "on_close".
     * Unfortunately, this requires casting to SessionImpl, as of Hibernate 3.1. If that cast doesn't
     * work, we'll simply assume we're safe and return <code>true</code>.
     * 
     * @param session the Hibernate Session to check
     * @see org.hibernate.impl.SessionImpl#getConnectionReleaseMode()
     * @see org.hibernate.ConnectionReleaseMode#ON_CLOSE
     */
    protected boolean isSameConnectionForEntireSession(Session session) {
        if (!(session instanceof SessionImpl)) {
            // The best we can do is to assume we're safe.
            return true;
        }
        ConnectionReleaseMode releaseMode = ((SessionImpl) session).getConnectionReleaseMode();
        return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode);
    }

    /**
     * Convert the given HibernateException to an appropriate exception
     * from the <code>org.springframework.dao</code> hierarchy.
     * <p>
     * Will automatically apply a specified SQLExceptionTranslator to a Hibernate JDBCException, else
     * rely on Hibernate's default translation.
     * 
     * @param ex HibernateException that occured
     * @return a corresponding DataAccessException
     * @see #setJdbcExceptionTranslator
     */
    protected DataAccessException convertHibernateAccessException(HibernateException ex) {
        if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
            return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
        } else if (GenericJDBCException.class.equals(ex.getClass())) {
            return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
        }
        return SessionUtils.convertHibernateAccessException(ex);
    }

    /**
     * Convert the given Hibernate JDBCException to an appropriate exception
     * from the <code>org.springframework.dao</code> hierarchy, using the
     * given SQLExceptionTranslator.
     * 
     * @param ex Hibernate JDBCException that occured
     * @param translator the SQLExceptionTranslator to use
     * @return a corresponding DataAccessException
     */
    protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) {
        return translator.translate("Hibernate flushing: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
    }

    /**
     * Obtain a default SQLExceptionTranslator, lazily creating it if necessary.
     * <p>
     * Creates a default {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator}
     * for the SessionFactory's underlying DataSource.
     */
    protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
        if (this.defaultJdbcExceptionTranslator == null) {
            this.defaultJdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(getDataSource());
        }
        return this.defaultJdbcExceptionTranslator;
    }

    /**
     * Hibernate transaction object, representing a SessionHolder.
     * Used as transaction object by HibernateTransactionManager.
     */
    private class HibernateTransactionObject extends JdbcTransactionObjectSupport {

        private SessionHolder sessionHolder;

        private boolean newSessionHolder;

        private boolean newSession;

        public void setSession(Session session) {
            this.sessionHolder = new SessionHolder(session);
            this.newSessionHolder = true;
            this.newSession = true;
        }

        public void setExistingSession(Session session) {
            this.sessionHolder = new SessionHolder(session);
            this.newSessionHolder = true;
            this.newSession = false;
        }

        public void setSessionHolder(SessionHolder sessionHolder) {
            this.sessionHolder = sessionHolder;
            this.newSessionHolder = false;
            this.newSession = false;
        }

        public SessionHolder getSessionHolder() {
            return this.sessionHolder;
        }

        public boolean isNewSessionHolder() {
            return this.newSessionHolder;
        }

        public boolean isNewSession() {
            return this.newSession;
        }

        public boolean hasSpringManagedTransaction() {
            return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
        }

        public boolean hasHibernateManagedTransaction() {
            return (this.sessionHolder != null && this.sessionHolder.getSession().getTransaction().isActive());
        }

        public void setRollbackOnly() {
            this.sessionHolder.setRollbackOnly();
            if (hasConnectionHolder()) {
                getConnectionHolder().setRollbackOnly();
            }
        }

        public boolean isRollbackOnly() {
            return this.sessionHolder.isRollbackOnly()
                    || (hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
        }

        public void flush() {
            try {
                this.sessionHolder.getSession().flush();
            } catch (HibernateException ex) {
                throw convertHibernateAccessException(ex);
            }
        }
    }

    /**
     * Holder for suspended resources.
     * Used internally by <code>doSuspend</code> and <code>doResume</code>.
     */
    private static class SuspendedResourcesHolder {

        private final SessionHolder sessionHolder;

        private final ConnectionHolder connectionHolder;

        private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
            this.sessionHolder = sessionHolder;
            this.connectionHolder = conHolder;
        }

        private SessionHolder getSessionHolder() {
            return this.sessionHolder;
        }

        private ConnectionHolder getConnectionHolder() {
            return this.connectionHolder;
        }
    }

}