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