001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.orm.hibernate4;
018
019import java.sql.Connection;
020import java.sql.ResultSet;
021import javax.sql.DataSource;
022
023import org.hibernate.ConnectionReleaseMode;
024import org.hibernate.FlushMode;
025import org.hibernate.HibernateException;
026import org.hibernate.Interceptor;
027import org.hibernate.Session;
028import org.hibernate.SessionFactory;
029import org.hibernate.Transaction;
030import org.hibernate.engine.spi.SessionImplementor;
031import org.hibernate.engine.transaction.spi.TransactionContext;
032
033import org.springframework.beans.BeansException;
034import org.springframework.beans.factory.BeanFactory;
035import org.springframework.beans.factory.BeanFactoryAware;
036import org.springframework.beans.factory.InitializingBean;
037import org.springframework.dao.DataAccessException;
038import org.springframework.dao.DataAccessResourceFailureException;
039import org.springframework.jdbc.datasource.ConnectionHolder;
040import org.springframework.jdbc.datasource.DataSourceUtils;
041import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
042import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
043import org.springframework.transaction.CannotCreateTransactionException;
044import org.springframework.transaction.IllegalTransactionStateException;
045import org.springframework.transaction.InvalidIsolationLevelException;
046import org.springframework.transaction.TransactionDefinition;
047import org.springframework.transaction.TransactionSystemException;
048import org.springframework.transaction.support.AbstractPlatformTransactionManager;
049import org.springframework.transaction.support.DefaultTransactionStatus;
050import org.springframework.transaction.support.ResourceTransactionManager;
051import org.springframework.transaction.support.TransactionSynchronizationManager;
052
053/**
054 * {@link org.springframework.transaction.PlatformTransactionManager}
055 * implementation for a single Hibernate {@link org.hibernate.SessionFactory}.
056 * Binds a Hibernate Session from the specified factory to the thread,
057 * potentially allowing for one thread-bound Session per factory.
058 * {@code SessionFactory.getCurrentSession()} is required for Hibernate
059 * access code that needs to support this transaction handling mechanism,
060 * with the SessionFactory being configured with {@link SpringSessionContext}.
061 *
062 * <p>Supports custom isolation levels, and timeouts that get applied as
063 * Hibernate transaction timeouts.
064 *
065 * <p>This transaction manager is appropriate for applications that use a single
066 * Hibernate SessionFactory for transactional data access, but it also supports
067 * direct DataSource access within a transaction (i.e. plain JDBC code working
068 * with the same DataSource). This allows for mixing services which access Hibernate
069 * and services which use plain JDBC (without being aware of Hibernate)!
070 * Application code needs to stick to the same simple Connection lookup pattern as
071 * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
072 * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
073 * or going through a
074 * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
075 *
076 * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
077 * this instance needs to be aware of the DataSource ({@link #setDataSource}).
078 * The given DataSource should obviously match the one used by the given SessionFactory.
079 *
080 * <p>JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager})
081 * is necessary for accessing multiple transactional resources within the same
082 * transaction. The DataSource that Hibernate uses needs to be JTA-enabled in
083 * such a scenario (see container setup).
084 *
085 * <p>This transaction manager supports nested transactions via JDBC 3.0 Savepoints.
086 * The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"} flag defaults
087 * to "false", though, as nested transactions will just apply to the JDBC Connection,
088 * not to the Hibernate Session and its cached entity objects and related context.
089 * You can manually set the flag to "true" if you want to use nested transactions
090 * for JDBC access code which participates in Hibernate transactions (provided that
091 * your JDBC driver supports Savepoints). <i>Note that Hibernate itself does not
092 * support nested transactions! Hence, do not expect Hibernate access code to
093 * semantically participate in a nested transaction.</i>
094 *
095 * <p><b>NOTE: Hibernate 4.2+ is strongly recommended for efficient transaction
096 * management with Spring, in particular for transactional Spring JDBC access.</b>
097 *
098 * @author Juergen Hoeller
099 * @since 3.1
100 * @see #setSessionFactory
101 * @see #setDataSource
102 * @see org.hibernate.SessionFactory#getCurrentSession()
103 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
104 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
105 * @see org.springframework.jdbc.core.JdbcTemplate
106 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
107 * @see org.springframework.transaction.jta.JtaTransactionManager
108 */
109@SuppressWarnings("serial")
110public class HibernateTransactionManager extends AbstractPlatformTransactionManager
111                implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
112
113        private SessionFactory sessionFactory;
114
115        private DataSource dataSource;
116
117        private boolean autodetectDataSource = true;
118
119        private boolean prepareConnection = true;
120
121        private boolean allowResultAccessAfterCompletion = false;
122
123        private boolean hibernateManagedSession = false;
124
125        private Object entityInterceptor;
126
127        /**
128         * Just needed for entityInterceptorBeanName.
129         * @see #setEntityInterceptorBeanName
130         */
131        private BeanFactory beanFactory;
132
133
134        /**
135         * Create a new HibernateTransactionManager instance.
136         * A SessionFactory has to be set to be able to use it.
137         * @see #setSessionFactory
138         */
139        public HibernateTransactionManager() {
140        }
141
142        /**
143         * Create a new HibernateTransactionManager instance.
144         * @param sessionFactory SessionFactory to manage transactions for
145         */
146        public HibernateTransactionManager(SessionFactory sessionFactory) {
147                this.sessionFactory = sessionFactory;
148                afterPropertiesSet();
149        }
150
151
152        /**
153         * Set the SessionFactory that this instance should manage transactions for.
154         */
155        public void setSessionFactory(SessionFactory sessionFactory) {
156                this.sessionFactory = sessionFactory;
157        }
158
159        /**
160         * Return the SessionFactory that this instance should manage transactions for.
161         */
162        public SessionFactory getSessionFactory() {
163                return this.sessionFactory;
164        }
165
166        /**
167         * Set the JDBC DataSource that this instance should manage transactions for.
168         * The DataSource should match the one used by the Hibernate SessionFactory:
169         * for example, you could specify the same JNDI DataSource for both.
170         * <p>If the SessionFactory was configured with LocalDataSourceConnectionProvider,
171         * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource",
172         * the DataSource will be auto-detected: You can still explicitly specify the
173         * DataSource, but you don't need to in this case.
174         * <p>A transactional JDBC Connection for this DataSource will be provided to
175         * application code accessing this DataSource directly via DataSourceUtils
176         * or JdbcTemplate. The Connection will be taken from the Hibernate Session.
177         * <p>The DataSource specified here should be the target DataSource to manage
178         * transactions for, not a TransactionAwareDataSourceProxy. Only data access
179         * code may work with TransactionAwareDataSourceProxy, while the transaction
180         * manager needs to work on the underlying target DataSource. If there's
181         * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
182         * unwrapped to extract its target DataSource.
183         * @see #setAutodetectDataSource
184         * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
185         * @see org.springframework.jdbc.datasource.DataSourceUtils
186         * @see org.springframework.jdbc.core.JdbcTemplate
187         */
188        public void setDataSource(DataSource dataSource) {
189                if (dataSource instanceof TransactionAwareDataSourceProxy) {
190                        // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
191                        // for its underlying target DataSource, else data access code won't see
192                        // properly exposed transactions (i.e. transactions for the target DataSource).
193                        this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
194                }
195                else {
196                        this.dataSource = dataSource;
197                }
198        }
199
200        /**
201         * Return the JDBC DataSource that this instance manages transactions for.
202         */
203        public DataSource getDataSource() {
204                return this.dataSource;
205        }
206
207        /**
208         * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory,
209         * if set via LocalSessionFactoryBean's {@code setDataSource}. Default is "true".
210         * <p>Can be turned off to deliberately ignore an available DataSource, in order
211         * to not expose Hibernate transactions as JDBC transactions for that DataSource.
212         * @see #setDataSource
213         */
214        public void setAutodetectDataSource(boolean autodetectDataSource) {
215                this.autodetectDataSource = autodetectDataSource;
216        }
217
218        /**
219         * Set whether to prepare the underlying JDBC Connection of a transactional
220         * Hibernate Session, that is, whether to apply a transaction-specific
221         * isolation level and/or the transaction's read-only flag to the underlying
222         * JDBC Connection.
223         * <p>Default is "true". If you turn this flag off, the transaction manager
224         * will not support per-transaction isolation levels anymore. It will not
225         * call {@code Connection.setReadOnly(true)} for read-only transactions
226         * anymore either. If this flag is turned off, no cleanup of a JDBC Connection
227         * is required after a transaction, since no Connection settings will get modified.
228         * @see java.sql.Connection#setTransactionIsolation
229         * @see java.sql.Connection#setReadOnly
230         */
231        public void setPrepareConnection(boolean prepareConnection) {
232                this.prepareConnection = prepareConnection;
233        }
234
235        /**
236         * Set whether to allow result access after completion, typically via Hibernate's
237         * ScrollableResults mechanism.
238         * <p>Default is "false". Turning this flag on enforces over-commit holdability on the
239         * underlying JDBC Connection (if {@link #prepareConnection "prepareConnection"} is on)
240         * and skips the disconnect-on-completion step.
241         * @since 4.1.2
242         * @see java.sql.Connection#setHoldability
243         * @see ResultSet#HOLD_CURSORS_OVER_COMMIT
244         * @see #disconnectOnCompletion(Session)
245         */
246        public void setAllowResultAccessAfterCompletion(boolean allowResultAccessAfterCompletion) {
247                this.allowResultAccessAfterCompletion = allowResultAccessAfterCompletion;
248        }
249
250        /**
251         * Set whether to operate on a Hibernate-managed Session instead of a
252         * Spring-managed Session, that is, whether to obtain the Session through
253         * Hibernate's {@link org.hibernate.SessionFactory#getCurrentSession()}
254         * instead of {@link org.hibernate.SessionFactory#openSession()} (with a Spring
255         * {@link org.springframework.transaction.support.TransactionSynchronizationManager}
256         * check preceding it).
257         * <p>Default is "false", i.e. using a Spring-managed Session: taking the current
258         * thread-bound Session if available (e.g. in an Open-Session-in-View scenario),
259         * creating a new Session for the current transaction otherwise.
260         * <p>Switch this flag to "true" in order to enforce use of a Hibernate-managed Session.
261         * Note that this requires {@link org.hibernate.SessionFactory#getCurrentSession()}
262         * to always return a proper Session when called for a Spring-managed transaction;
263         * transaction begin will fail if the {@code getCurrentSession()} call fails.
264         * <p>This mode will typically be used in combination with a custom Hibernate
265         * {@link org.hibernate.context.spi.CurrentSessionContext} implementation that stores
266         * Sessions in a place other than Spring's TransactionSynchronizationManager.
267         * It may also be used in combination with Spring's Open-Session-in-View support
268         * (using Spring's default {@link SpringSessionContext}), in which case it subtly
269         * differs from the Spring-managed Session mode: The pre-bound Session will <i>not</i>
270         * receive a {@code clear()} call (on rollback) or a {@code disconnect()}
271         * call (on transaction completion) in such a scenario; this is rather left up
272         * to a custom CurrentSessionContext implementation (if desired).
273         */
274        public void setHibernateManagedSession(boolean hibernateManagedSession) {
275                this.hibernateManagedSession = hibernateManagedSession;
276        }
277
278        /**
279         * Set the bean name of a Hibernate entity interceptor that allows to inspect
280         * and change property values before writing to and reading from the database.
281         * Will get applied to any new Session created by this transaction manager.
282         * <p>Requires the bean factory to be known, to be able to resolve the bean
283         * name to an interceptor instance on session creation. Typically used for
284         * prototype interceptors, i.e. a new interceptor instance per session.
285         * <p>Can also be used for shared interceptor instances, but it is recommended
286         * to set the interceptor reference directly in such a scenario.
287         * @param entityInterceptorBeanName the name of the entity interceptor in
288         * the bean factory
289         * @see #setBeanFactory
290         * @see #setEntityInterceptor
291         */
292        public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
293                this.entityInterceptor = entityInterceptorBeanName;
294        }
295
296        /**
297         * Set a Hibernate entity interceptor that allows to inspect and change
298         * property values before writing to and reading from the database.
299         * Will get applied to any new Session created by this transaction manager.
300         * <p>Such an interceptor can either be set at the SessionFactory level,
301         * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
302         * HibernateTransactionManager.
303         * @see LocalSessionFactoryBean#setEntityInterceptor
304         */
305        public void setEntityInterceptor(Interceptor entityInterceptor) {
306                this.entityInterceptor = entityInterceptor;
307        }
308
309        /**
310         * Return the current Hibernate entity interceptor, or {@code null} if none.
311         * Resolves an entity interceptor bean name via the bean factory,
312         * if necessary.
313         * @throws IllegalStateException if bean name specified but no bean factory set
314         * @throws BeansException if bean name resolution via the bean factory failed
315         * @see #setEntityInterceptor
316         * @see #setEntityInterceptorBeanName
317         * @see #setBeanFactory
318         */
319        public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
320                if (this.entityInterceptor instanceof Interceptor) {
321                        return (Interceptor) entityInterceptor;
322                }
323                else if (this.entityInterceptor instanceof String) {
324                        if (this.beanFactory == null) {
325                                throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
326                        }
327                        String beanName = (String) this.entityInterceptor;
328                        return this.beanFactory.getBean(beanName, Interceptor.class);
329                }
330                else {
331                        return null;
332                }
333        }
334
335        /**
336         * The bean factory just needs to be known for resolving entity interceptor
337         * bean names. It does not need to be set for any other mode of operation.
338         * @see #setEntityInterceptorBeanName
339         */
340        @Override
341        public void setBeanFactory(BeanFactory beanFactory) {
342                this.beanFactory = beanFactory;
343        }
344
345        @Override
346        public void afterPropertiesSet() {
347                if (getSessionFactory() == null) {
348                        throw new IllegalArgumentException("Property 'sessionFactory' is required");
349                }
350                if (this.entityInterceptor instanceof String && this.beanFactory == null) {
351                        throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'");
352                }
353
354                // Check for SessionFactory's DataSource.
355                if (this.autodetectDataSource && getDataSource() == null) {
356                        DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory());
357                        if (sfds != null) {
358                                // Use the SessionFactory's DataSource for exposing transactions to JDBC code.
359                                if (logger.isInfoEnabled()) {
360                                        logger.info("Using DataSource [" + sfds +
361                                                        "] of Hibernate SessionFactory for HibernateTransactionManager");
362                                }
363                                setDataSource(sfds);
364                        }
365                }
366        }
367
368
369        @Override
370        public Object getResourceFactory() {
371                return getSessionFactory();
372        }
373
374        @Override
375        protected Object doGetTransaction() {
376                HibernateTransactionObject txObject = new HibernateTransactionObject();
377                txObject.setSavepointAllowed(isNestedTransactionAllowed());
378
379                SessionHolder sessionHolder =
380                                (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
381                if (sessionHolder != null) {
382                        if (logger.isDebugEnabled()) {
383                                logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
384                        }
385                        txObject.setSessionHolder(sessionHolder);
386                }
387                else if (this.hibernateManagedSession) {
388                        try {
389                                Session session = this.sessionFactory.getCurrentSession();
390                                if (logger.isDebugEnabled()) {
391                                        logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
392                                }
393                                txObject.setExistingSession(session);
394                        }
395                        catch (HibernateException ex) {
396                                throw new DataAccessResourceFailureException(
397                                                "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
398                        }
399                }
400
401                if (getDataSource() != null) {
402                        ConnectionHolder conHolder = (ConnectionHolder)
403                                        TransactionSynchronizationManager.getResource(getDataSource());
404                        txObject.setConnectionHolder(conHolder);
405                }
406
407                return txObject;
408        }
409
410        @Override
411        protected boolean isExistingTransaction(Object transaction) {
412                HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
413                return (txObject.hasSpringManagedTransaction() ||
414                                (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction()));
415        }
416
417        @Override
418        protected void doBegin(Object transaction, TransactionDefinition definition) {
419                HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
420
421                if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
422                        throw new IllegalTransactionStateException(
423                                        "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
424                                        "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
425                                        "It is recommended to use a single HibernateTransactionManager for all transactions " +
426                                        "on a single DataSource, no matter whether Hibernate or JDBC access.");
427                }
428
429                Session session = null;
430
431                try {
432                        if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
433                                Interceptor entityInterceptor = getEntityInterceptor();
434                                Session newSession = (entityInterceptor != null ?
435                                                getSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
436                                                getSessionFactory().openSession());
437                                if (logger.isDebugEnabled()) {
438                                        logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
439                                }
440                                txObject.setSession(newSession);
441                        }
442
443                        session = txObject.getSessionHolder().getSession();
444
445                        if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
446                                // We're allowed to change the transaction settings of the JDBC Connection.
447                                if (logger.isDebugEnabled()) {
448                                        logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
449                                }
450                                Connection con = ((SessionImplementor) session).connection();
451                                Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
452                                txObject.setPreviousIsolationLevel(previousIsolationLevel);
453                                if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
454                                        int currentHoldability = con.getHoldability();
455                                        if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
456                                                txObject.setPreviousHoldability(currentHoldability);
457                                                con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
458                                        }
459                                }
460                        }
461                        else {
462                                // Not allowed to change the transaction settings of the JDBC Connection.
463                                if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
464                                        // We should set a specific isolation level but are not allowed to...
465                                        throw new InvalidIsolationLevelException(
466                                                        "HibernateTransactionManager is not allowed to support custom isolation levels: " +
467                                                        "make sure that its 'prepareConnection' flag is on (the default) and that the " +
468                                                        "Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
469                                }
470                                if (logger.isDebugEnabled()) {
471                                        logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
472                                }
473                        }
474
475                        if (definition.isReadOnly() && txObject.isNewSession()) {
476                                // Just set to MANUAL in case of a new Session for this transaction.
477                                session.setFlushMode(FlushMode.MANUAL);
478                        }
479
480                        if (!definition.isReadOnly() && !txObject.isNewSession()) {
481                                // We need AUTO or COMMIT for a non-read-only transaction.
482                                FlushMode flushMode = session.getFlushMode();
483                                if (FlushMode.MANUAL.equals(flushMode)) {
484                                        session.setFlushMode(FlushMode.AUTO);
485                                        txObject.getSessionHolder().setPreviousFlushMode(flushMode);
486                                }
487                        }
488
489                        Transaction hibTx;
490
491                        // Register transaction timeout.
492                        int timeout = determineTimeout(definition);
493                        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
494                                // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
495                                // Applies to all statements, also to inserts, updates and deletes!
496                                hibTx = session.getTransaction();
497                                hibTx.setTimeout(timeout);
498                                hibTx.begin();
499                        }
500                        else {
501                                // Open a plain Hibernate transaction without specified timeout.
502                                hibTx = session.beginTransaction();
503                        }
504
505                        // Add the Hibernate transaction to the session holder.
506                        txObject.getSessionHolder().setTransaction(hibTx);
507
508                        // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
509                        if (getDataSource() != null) {
510                                Connection con = ((SessionImplementor) session).connection();
511                                ConnectionHolder conHolder = new ConnectionHolder(con);
512                                if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
513                                        conHolder.setTimeoutInSeconds(timeout);
514                                }
515                                if (logger.isDebugEnabled()) {
516                                        logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]");
517                                }
518                                TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
519                                txObject.setConnectionHolder(conHolder);
520                        }
521
522                        // Bind the session holder to the thread.
523                        if (txObject.isNewSessionHolder()) {
524                                TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
525                        }
526                        txObject.getSessionHolder().setSynchronizedWithTransaction(true);
527                }
528
529                catch (Throwable ex) {
530                        if (txObject.isNewSession()) {
531                                try {
532                                        if (session.getTransaction().isActive()) {
533                                                session.getTransaction().rollback();
534                                        }
535                                }
536                                catch (Throwable ex2) {
537                                        logger.debug("Could not rollback Session after failed transaction begin", ex);
538                                }
539                                finally {
540                                        SessionFactoryUtils.closeSession(session);
541                                        txObject.setSessionHolder(null);
542                                }
543                        }
544                        throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
545                }
546        }
547
548        @Override
549        protected Object doSuspend(Object transaction) {
550                HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
551                txObject.setSessionHolder(null);
552                SessionHolder sessionHolder =
553                                (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
554                txObject.setConnectionHolder(null);
555                ConnectionHolder connectionHolder = null;
556                if (getDataSource() != null) {
557                        connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
558                }
559                return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
560        }
561
562        @Override
563        protected void doResume(Object transaction, Object suspendedResources) {
564                SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
565                if (TransactionSynchronizationManager.hasResource(getSessionFactory())) {
566                        // From non-transactional code running in active transaction synchronization
567                        // -> can be safely removed, will be closed on transaction completion.
568                        TransactionSynchronizationManager.unbindResource(getSessionFactory());
569                }
570                TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder());
571                if (getDataSource() != null) {
572                        TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
573                }
574        }
575
576        @Override
577        protected void doCommit(DefaultTransactionStatus status) {
578                HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
579                if (status.isDebug()) {
580                        logger.debug("Committing Hibernate transaction on Session [" +
581                                        txObject.getSessionHolder().getSession() + "]");
582                }
583                try {
584                        txObject.getSessionHolder().getTransaction().commit();
585                }
586                catch (org.hibernate.TransactionException ex) {
587                        // assumably from commit call to the underlying JDBC connection
588                        throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
589                }
590                catch (HibernateException ex) {
591                        // assumably failed to flush changes to database
592                        throw convertHibernateAccessException(ex);
593                }
594        }
595
596        @Override
597        protected void doRollback(DefaultTransactionStatus status) {
598                HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
599                if (status.isDebug()) {
600                        logger.debug("Rolling back Hibernate transaction on Session [" +
601                                        txObject.getSessionHolder().getSession() + "]");
602                }
603                try {
604                        txObject.getSessionHolder().getTransaction().rollback();
605                }
606                catch (org.hibernate.TransactionException ex) {
607                        throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
608                }
609                catch (HibernateException ex) {
610                        // Shouldn't really happen, as a rollback doesn't cause a flush.
611                        throw convertHibernateAccessException(ex);
612                }
613                finally {
614                        if (!txObject.isNewSession() && !this.hibernateManagedSession) {
615                                // Clear all pending inserts/updates/deletes in the Session.
616                                // Necessary for pre-bound Sessions, to avoid inconsistent state.
617                                txObject.getSessionHolder().getSession().clear();
618                        }
619                }
620        }
621
622        @Override
623        protected void doSetRollbackOnly(DefaultTransactionStatus status) {
624                HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
625                if (status.isDebug()) {
626                        logger.debug("Setting Hibernate transaction on Session [" +
627                                        txObject.getSessionHolder().getSession() + "] rollback-only");
628                }
629                txObject.setRollbackOnly();
630        }
631
632        @Override
633        protected void doCleanupAfterCompletion(Object transaction) {
634                HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
635
636                // Remove the session holder from the thread.
637                if (txObject.isNewSessionHolder()) {
638                        TransactionSynchronizationManager.unbindResource(getSessionFactory());
639                }
640
641                // Remove the JDBC connection holder from the thread, if exposed.
642                if (getDataSource() != null) {
643                        TransactionSynchronizationManager.unbindResource(getDataSource());
644                }
645
646                Session session = txObject.getSessionHolder().getSession();
647                if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) {
648                        // We're running with connection release mode "on_close": We're able to reset
649                        // the isolation level and/or read-only flag of the JDBC Connection here.
650                        // Else, we need to rely on the connection pool to perform proper cleanup.
651                        try {
652                                Connection con = ((SessionImplementor) session).connection();
653                                Integer previousHoldability = txObject.getPreviousHoldability();
654                                if (previousHoldability != null) {
655                                        con.setHoldability(previousHoldability);
656                                }
657                                DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
658                        }
659                        catch (HibernateException ex) {
660                                logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
661                        }
662                        catch (Throwable ex) {
663                                logger.debug("Could not reset JDBC Connection after transaction", ex);
664                        }
665                }
666
667                if (txObject.isNewSession()) {
668                        if (logger.isDebugEnabled()) {
669                                logger.debug("Closing Hibernate Session [" + session + "] after transaction");
670                        }
671                        SessionFactoryUtils.closeSession(session);
672                }
673                else {
674                        if (logger.isDebugEnabled()) {
675                                logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction");
676                        }
677                        if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
678                                session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
679                        }
680                        if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
681                                disconnectOnCompletion(session);
682                        }
683                }
684                txObject.getSessionHolder().clear();
685        }
686
687        /**
688         * Disconnect a pre-existing Hibernate Session on transaction completion,
689         * returning its database connection but preserving its entity state.
690         * <p>The default implementation simply calls {@link Session#disconnect()}.
691         * Subclasses may override this with a no-op or with fine-tuned disconnection logic.
692         * @param session the Hibernate Session to disconnect
693         * @since 4.1.2
694         * @see org.hibernate.Session#disconnect()
695         */
696        protected void disconnectOnCompletion(Session session) {
697                session.disconnect();
698        }
699
700        /**
701         * Return whether the given Hibernate Session will always hold the same
702         * JDBC Connection. This is used to check whether the transaction manager
703         * can safely prepare and clean up the JDBC Connection used for a transaction.
704         * <p>The default implementation checks the Session's connection release mode
705         * to be "on_close".
706         * @param session the Hibernate Session to check
707         * @see org.hibernate.engine.transaction.spi.TransactionContext#getConnectionReleaseMode()
708         * @see org.hibernate.ConnectionReleaseMode#ON_CLOSE
709         */
710        protected boolean isSameConnectionForEntireSession(Session session) {
711                if (!(session instanceof TransactionContext)) {
712                        // The best we can do is to assume we're safe.
713                        return true;
714                }
715                ConnectionReleaseMode releaseMode = ((TransactionContext) session).getConnectionReleaseMode();
716                return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode);
717        }
718
719
720        /**
721         * Convert the given HibernateException to an appropriate exception
722         * from the {@code org.springframework.dao} hierarchy.
723         * <p>Will automatically apply a specified SQLExceptionTranslator to a
724         * Hibernate JDBCException, else rely on Hibernate's default translation.
725         * @param ex HibernateException that occurred
726         * @return a corresponding DataAccessException
727         * @see SessionFactoryUtils#convertHibernateAccessException
728         */
729        protected DataAccessException convertHibernateAccessException(HibernateException ex) {
730                return SessionFactoryUtils.convertHibernateAccessException(ex);
731        }
732
733
734        /**
735         * Hibernate transaction object, representing a SessionHolder.
736         * Used as transaction object by HibernateTransactionManager.
737         */
738        private class HibernateTransactionObject extends JdbcTransactionObjectSupport {
739
740                private SessionHolder sessionHolder;
741
742                private boolean newSessionHolder;
743
744                private boolean newSession;
745
746                private Integer previousHoldability;
747
748                public void setSession(Session session) {
749                        this.sessionHolder = new SessionHolder(session);
750                        this.newSessionHolder = true;
751                        this.newSession = true;
752                }
753
754                public void setExistingSession(Session session) {
755                        this.sessionHolder = new SessionHolder(session);
756                        this.newSessionHolder = true;
757                        this.newSession = false;
758                }
759
760                public void setSessionHolder(SessionHolder sessionHolder) {
761                        this.sessionHolder = sessionHolder;
762                        this.newSessionHolder = false;
763                        this.newSession = false;
764                }
765
766                public SessionHolder getSessionHolder() {
767                        return this.sessionHolder;
768                }
769
770                public boolean isNewSessionHolder() {
771                        return this.newSessionHolder;
772                }
773
774                public boolean isNewSession() {
775                        return this.newSession;
776                }
777
778                public void setPreviousHoldability(Integer previousHoldability) {
779                        this.previousHoldability = previousHoldability;
780                }
781
782                public Integer getPreviousHoldability() {
783                        return this.previousHoldability;
784                }
785
786                public boolean hasSpringManagedTransaction() {
787                        return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
788                }
789
790                public boolean hasHibernateManagedTransaction() {
791                        return (this.sessionHolder != null && this.sessionHolder.getSession().getTransaction().isActive());
792                }
793
794                public void setRollbackOnly() {
795                        this.sessionHolder.setRollbackOnly();
796                        if (hasConnectionHolder()) {
797                                getConnectionHolder().setRollbackOnly();
798                        }
799                }
800
801                @Override
802                public boolean isRollbackOnly() {
803                        return this.sessionHolder.isRollbackOnly() ||
804                                        (hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
805                }
806
807                @Override
808                public void flush() {
809                        try {
810                                this.sessionHolder.getSession().flush();
811                        }
812                        catch (HibernateException ex) {
813                                throw convertHibernateAccessException(ex);
814                        }
815                }
816        }
817
818
819        /**
820         * Holder for suspended resources.
821         * Used internally by {@code doSuspend} and {@code doResume}.
822         */
823        private static class SuspendedResourcesHolder {
824
825                private final SessionHolder sessionHolder;
826
827                private final ConnectionHolder connectionHolder;
828
829                private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
830                        this.sessionHolder = sessionHolder;
831                        this.connectionHolder = conHolder;
832                }
833
834                private SessionHolder getSessionHolder() {
835                        return this.sessionHolder;
836                }
837
838                private ConnectionHolder getConnectionHolder() {
839                        return this.connectionHolder;
840                }
841        }
842
843}