KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > orm > hibernate3 > HibernateTransactionManager


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.orm.hibernate3;
18
19 import java.sql.Connection JavaDoc;
20
21 import javax.sql.DataSource JavaDoc;
22
23 import org.hibernate.ConnectionReleaseMode;
24 import org.hibernate.FlushMode;
25 import org.hibernate.HibernateException;
26 import org.hibernate.Interceptor;
27 import org.hibernate.JDBCException;
28 import org.hibernate.Session;
29 import org.hibernate.SessionFactory;
30 import org.hibernate.Transaction;
31 import org.hibernate.exception.GenericJDBCException;
32 import org.hibernate.impl.SessionImpl;
33
34 import org.springframework.beans.BeansException;
35 import org.springframework.beans.factory.BeanFactory;
36 import org.springframework.beans.factory.BeanFactoryAware;
37 import org.springframework.beans.factory.InitializingBean;
38 import org.springframework.dao.DataAccessException;
39 import org.springframework.jdbc.datasource.ConnectionHolder;
40 import org.springframework.jdbc.datasource.DataSourceUtils;
41 import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
42 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
43 import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
44 import org.springframework.jdbc.support.SQLExceptionTranslator;
45 import org.springframework.transaction.CannotCreateTransactionException;
46 import org.springframework.transaction.IllegalTransactionStateException;
47 import org.springframework.transaction.InvalidIsolationLevelException;
48 import org.springframework.transaction.TransactionDefinition;
49 import org.springframework.transaction.TransactionSystemException;
50 import org.springframework.transaction.support.AbstractPlatformTransactionManager;
51 import org.springframework.transaction.support.DefaultTransactionStatus;
52 import org.springframework.transaction.support.TransactionSynchronizationManager;
53 import org.springframework.transaction.support.ResourceTransactionManager;
54 import org.springframework.util.ClassUtils;
55
56 /**
57  * {@link org.springframework.transaction.PlatformTransactionManager}
58  * implementation for a single Hibernate {@link org.hibernate.SessionFactory}.
59  * Binds a Hibernate Session from the specified factory to the thread, potentially
60  * allowing for one thread-bound Session per factory. {@link SessionFactoryUtils}
61  * and {@link HibernateTemplate} are aware of thread-bound Sessions and participate
62  * in such transactions automatically. Using either of those or going through
63  * <code>SessionFactory.getCurrentSession()</code> is required for Hibernate
64  * access code that needs to support this transaction handling mechanism.
65  *
66  * <p>Supports custom isolation levels, and timeouts that get applied as
67  * Hibernate transaction timeouts (on Hibernate 3.1+) or as appropriate
68  * query timeouts (on Hibernate 3.0, when using {@link HibernateTemplate}).
69  *
70  * <p>This transaction manager is appropriate for applications that use a single
71  * Hibernate SessionFactory for transactional data access, but it also supports
72  * direct DataSource access within a transaction (i.e. plain JDBC code working
73  * with the same DataSource). This allows for mixing services which access Hibernate
74  * and services which use plain JDBC (without being aware of Hibernate)!
75  * Application code needs to stick to the same simple Connection lookup pattern as
76  * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
77  * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
78  * or going through a
79  * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
80  *
81  * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
82  * this instance needs to be aware of the DataSource ({@link #setDataSource}).
83  * The given DataSource should obviously match the one used by the given
84  * SessionFactory. To achieve this, configure both to the same JNDI DataSource,
85  * or preferably create the SessionFactory with {@link LocalSessionFactoryBean} and
86  * a local DataSource (which will be autodetected by this transaction manager).
87  *
88  * <p>JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager})
89  * is necessary for accessing multiple transactional resources within the same
90  * transaction. The DataSource that Hibernate uses needs to be JTA-enabled in
91  * such a scenario (see container setup). Normally, JTA setup for Hibernate is
92  * somewhat container-specific due to the JTA TransactionManager lookup, required
93  * for proper transactional handling of the SessionFactory-level read-write cache.
94  *
95  * <p>Fortunately, there is an easier way with Spring: {@link SessionFactoryUtils}
96  * (and thus {@link HibernateTemplate}) registers synchronizations with Spring's
97  * {@link org.springframework.transaction.support.TransactionSynchronizationManager}
98  * (as used by {@link org.springframework.transaction.jta.JtaTransactionManager}),
99  * for proper after-completion callbacks. Therefore, as long as Spring's
100  * JtaTransactionManager drives the JTA transactions, Hibernate does not require
101  * any special configuration for proper JTA participation. Note that there are
102  * special restrictions with EJB CMT and restrictive JTA subsystems: See
103  * {@link org.springframework.transaction.jta.JtaTransactionManager}'s javadoc for details.
104  *
105  * <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
106  * Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"}
107  * flag defaults to "false", though, as nested transactions will just apply to the
108  * JDBC Connection, not to the Hibernate Session and its cached objects. You can
109  * manually set the flag to "true" if you want to use nested transactions for
110  * JDBC access code which participates in Hibernate transactions (provided that
111  * your JDBC driver supports Savepoints). <i>Note that Hibernate itself does not
112  * support nested transactions! Hence, do not expect Hibernate access code to
113  * semantically participate in a nested transaction.</i>
114  *
115  * <p>Requires Hibernate 3.0.3 or later. As of Spring 2.0, this transaction manager
116  * autodetects Hibernate 3.1 and uses its advanced timeout functionality, while
117  * remaining compatible with Hibernate 3.0 as well. Running against Hibernate 3.1.3+
118  * is recommended, unless you need to remain compatible with JDK 1.3. (Note that
119  * Hibernate 3.1+ only runs on JDK 1.4+!)
120  *
121  * @author Juergen Hoeller
122  * @since 1.2
123  * @see #setSessionFactory
124  * @see #setDataSource
125  * @see LocalSessionFactoryBean
126  * @see SessionFactoryUtils#getSession
127  * @see SessionFactoryUtils#applyTransactionTimeout
128  * @see SessionFactoryUtils#releaseSession
129  * @see HibernateTemplate
130  * @see org.hibernate.SessionFactory#getCurrentSession()
131  * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
132  * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout
133  * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
134  * @see org.springframework.jdbc.core.JdbcTemplate
135  * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
136  * @see org.springframework.transaction.jta.JtaTransactionManager
137  */

138 public class HibernateTransactionManager extends AbstractPlatformTransactionManager
139         implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
140
141     // Determine whether the Hibernate 3.1 Transaction.setTimeout(int) method
142
// is available, for use in this HibernateTransactionManager's doBegin.
143
private final static boolean hibernateSetTimeoutAvailable =
144             ClassUtils.hasMethod(Transaction.class, "setTimeout", new Class JavaDoc[] {int.class});
145
146
147     private SessionFactory sessionFactory;
148
149     private DataSource JavaDoc dataSource;
150
151     private boolean autodetectDataSource = true;
152
153     private boolean prepareConnection = true;
154
155     private Object JavaDoc entityInterceptor;
156
157     private SQLExceptionTranslator jdbcExceptionTranslator;
158
159     private SQLExceptionTranslator defaultJdbcExceptionTranslator;
160
161     /**
162      * Just needed for entityInterceptorBeanName.
163      * @see #setEntityInterceptorBeanName
164      */

165     private BeanFactory beanFactory;
166
167
168     /**
169      * Create a new HibernateTransactionManager instance.
170      * A SessionFactory has to be set to be able to use it.
171      * @see #setSessionFactory
172      */

173     public HibernateTransactionManager() {
174     }
175
176     /**
177      * Create a new HibernateTransactionManager instance.
178      * @param sessionFactory SessionFactory to manage transactions for
179      */

180     public HibernateTransactionManager(SessionFactory sessionFactory) {
181         this.sessionFactory = sessionFactory;
182         afterPropertiesSet();
183     }
184
185     /**
186      * Set the SessionFactory that this instance should manage transactions for.
187      */

188     public void setSessionFactory(SessionFactory sessionFactory) {
189         this.sessionFactory = sessionFactory;
190     }
191
192     /**
193      * Return the SessionFactory that this instance should manage transactions for.
194      */

195     public SessionFactory getSessionFactory() {
196         return this.sessionFactory;
197     }
198
199     /**
200      * Set the JDBC DataSource that this instance should manage transactions for.
201      * The DataSource should match the one used by the Hibernate SessionFactory:
202      * for example, you could specify the same JNDI DataSource for both.
203      * <p>If the SessionFactory was configured with LocalDataSourceConnectionProvider,
204      * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource",
205      * the DataSource will be auto-detected: You can still explictly specify the
206      * DataSource, but you don't need to in this case.
207      * <p>A transactional JDBC Connection for this DataSource will be provided to
208      * application code accessing this DataSource directly via DataSourceUtils
209      * or JdbcTemplate. The Connection will be taken from the Hibernate Session.
210      * <p>The DataSource specified here should be the target DataSource to manage
211      * transactions for, not a TransactionAwareDataSourceProxy. Only data access
212      * code may work with TransactionAwareDataSourceProxy, while the transaction
213      * manager needs to work on the underlying target DataSource. If there's
214      * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
215      * unwrapped to extract its target DataSource.
216      * @see #setAutodetectDataSource
217      * @see LocalDataSourceConnectionProvider
218      * @see LocalSessionFactoryBean#setDataSource
219      * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
220      * @see org.springframework.jdbc.datasource.DataSourceUtils
221      * @see org.springframework.jdbc.core.JdbcTemplate
222      */

223     public void setDataSource(DataSource JavaDoc dataSource) {
224         if (dataSource instanceof TransactionAwareDataSourceProxy) {
225             // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
226
// for its underlying target DataSource, else data access code won't see
227
// properly exposed transactions (i.e. transactions for the target DataSource).
228
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
229         }
230         else {
231             this.dataSource = dataSource;
232         }
233     }
234
235     /**
236      * Return the JDBC DataSource that this instance manages transactions for.
237      */

238     public DataSource JavaDoc getDataSource() {
239         return this.dataSource;
240     }
241
242     /**
243      * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory,
244      * if set via LocalSessionFactoryBean's <code>setDataSource</code>. Default is "true".
245      * <p>Can be turned off to deliberately ignore an available DataSource,
246      * to not expose Hibernate transactions as JDBC transactions for that DataSource.
247      * @see #setDataSource
248      * @see LocalSessionFactoryBean#setDataSource
249      */

250     public void setAutodetectDataSource(boolean autodetectDataSource) {
251         this.autodetectDataSource = autodetectDataSource;
252     }
253
254     /**
255      * Set whether to prepare the underlying JDBC Connection of a transactional
256      * Hibernate Session, that is, whether to apply a transaction-specific
257      * isolation level and/or the transaction's read-only flag to the underlying
258      * JDBC Connection.
259      * <p>Default is "true". If you turn this flag off, the transaction manager
260      * will not support per-transaction isolation levels anymore. It will not
261      * call <code>Connection.setReadOnly(true)</code> for read-only transactions
262      * anymore either. If this flag is turned off, no cleanup of a JDBC Connection
263      * is required after a transaction, since no Connection settings will get modified.
264      * <p>It is recommended to turn this flag off if running against Hibernate 3.1
265      * and a connection pool that does not reset connection settings (for example,
266      * Jakarta Commons DBCP). To keep this flag turned on, you can set the
267      * "hibernate.connection.release_mode" property to "on_close" instead,
268      * or consider using a smarter connection pool (for example, C3P0).
269      * @see java.sql.Connection#setTransactionIsolation
270      * @see java.sql.Connection#setReadOnly
271      */

272     public void setPrepareConnection(boolean prepareConnection) {
273         this.prepareConnection = prepareConnection;
274     }
275
276     /**
277      * Set the bean name of a Hibernate entity interceptor that allows to inspect
278      * and change property values before writing to and reading from the database.
279      * Will get applied to any new Session created by this transaction manager.
280      * <p>Requires the bean factory to be known, to be able to resolve the bean
281      * name to an interceptor instance on session creation. Typically used for
282      * prototype interceptors, i.e. a new interceptor instance per session.
283      * <p>Can also be used for shared interceptor instances, but it is recommended
284      * to set the interceptor reference directly in such a scenario.
285      * @param entityInterceptorBeanName the name of the entity interceptor in
286      * the bean factory
287      * @see #setBeanFactory
288      * @see #setEntityInterceptor
289      */

290     public void setEntityInterceptorBeanName(String JavaDoc entityInterceptorBeanName) {
291         this.entityInterceptor = entityInterceptorBeanName;
292     }
293
294     /**
295      * Set a Hibernate entity interceptor that allows to inspect and change
296      * property values before writing to and reading from the database.
297      * Will get applied to any new Session created by this transaction manager.
298      * <p>Such an interceptor can either be set at the SessionFactory level,
299      * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
300      * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
301      * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
302      * to avoid repeated configuration and guarantee consistent behavior in transactions.
303      * @see LocalSessionFactoryBean#setEntityInterceptor
304      * @see HibernateTemplate#setEntityInterceptor
305      * @see HibernateInterceptor#setEntityInterceptor
306      */

307     public void setEntityInterceptor(Interceptor entityInterceptor) {
308         this.entityInterceptor = entityInterceptor;
309     }
310
311     /**
312      * Return the current Hibernate entity interceptor, or <code>null</code> if none.
313      * Resolves an entity interceptor bean name via the bean factory,
314      * if necessary.
315      * @throws IllegalStateException if bean name specified but no bean factory set
316      * @throws BeansException if bean name resolution via the bean factory failed
317      * @see #setEntityInterceptor
318      * @see #setEntityInterceptorBeanName
319      * @see #setBeanFactory
320      */

321     public Interceptor getEntityInterceptor() throws IllegalStateException JavaDoc, BeansException {
322         if (this.entityInterceptor instanceof Interceptor) {
323             return (Interceptor) entityInterceptor;
324         }
325         else if (this.entityInterceptor instanceof String JavaDoc) {
326             if (this.beanFactory == null) {
327                 throw new IllegalStateException JavaDoc("Cannot get entity interceptor via bean name if no bean factory set");
328             }
329             String JavaDoc beanName = (String JavaDoc) this.entityInterceptor;
330             return (Interceptor) this.beanFactory.getBean(beanName, Interceptor.class);
331         }
332         else {
333             return null;
334         }
335     }
336
337     /**
338      * Set the JDBC exception translator for this transaction manager.
339      * <p>Applied to any SQLException root cause of a Hibernate JDBCException that
340      * is thrown on flush, overriding Hibernate's default SQLException translation
341      * (which is based on Hibernate's Dialect for a specific target database).
342      * @param jdbcExceptionTranslator the exception translator
343      * @see java.sql.SQLException
344      * @see org.hibernate.JDBCException
345      * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
346      * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
347      */

348     public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
349         this.jdbcExceptionTranslator = jdbcExceptionTranslator;
350     }
351
352     /**
353      * Return the JDBC exception translator for this transaction manager, if any.
354      */

355     public SQLExceptionTranslator getJdbcExceptionTranslator() {
356         return this.jdbcExceptionTranslator;
357     }
358
359     /**
360      * The bean factory just needs to be known for resolving entity interceptor
361      * bean names. It does not need to be set for any other mode of operation.
362      * @see #setEntityInterceptorBeanName
363      */

364     public void setBeanFactory(BeanFactory beanFactory) {
365         this.beanFactory = beanFactory;
366     }
367
368     public void afterPropertiesSet() {
369         if (getSessionFactory() == null) {
370             throw new IllegalArgumentException JavaDoc("Property 'sessionFactory' is required");
371         }
372         if (this.entityInterceptor instanceof String JavaDoc && this.beanFactory == null) {
373             throw new IllegalArgumentException JavaDoc("Property 'beanFactory' is required for 'entityInterceptorBeanName'");
374         }
375
376         // Check for SessionFactory's DataSource.
377
if (this.autodetectDataSource && getDataSource() == null) {
378             DataSource JavaDoc sfds = SessionFactoryUtils.getDataSource(getSessionFactory());
379             if (sfds != null) {
380                 // Use the SessionFactory's DataSource for exposing transactions to JDBC code.
381
if (logger.isInfoEnabled()) {
382                     logger.info("Using DataSource [" + sfds +
383                             "] of Hibernate SessionFactory for HibernateTransactionManager");
384                 }
385                 setDataSource(sfds);
386             }
387         }
388     }
389
390
391     public Object JavaDoc getResourceFactory() {
392         return getSessionFactory();
393     }
394
395     protected Object JavaDoc doGetTransaction() {
396         HibernateTransactionObject txObject = new HibernateTransactionObject();
397         txObject.setSavepointAllowed(isNestedTransactionAllowed());
398
399         SessionHolder sessionHolder =
400                 (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
401         if (sessionHolder != null) {
402             if (logger.isDebugEnabled()) {
403                 logger.debug("Found thread-bound Session [" +
404                         SessionFactoryUtils.toString(sessionHolder.getSession()) + "] for Hibernate transaction");
405             }
406             txObject.setSessionHolder(sessionHolder, false);
407         }
408
409         if (getDataSource() != null) {
410             ConnectionHolder conHolder = (ConnectionHolder)
411                     TransactionSynchronizationManager.getResource(getDataSource());
412             txObject.setConnectionHolder(conHolder);
413         }
414
415         return txObject;
416     }
417
418     protected boolean isExistingTransaction(Object JavaDoc transaction) {
419         return ((HibernateTransactionObject) transaction).hasTransaction();
420     }
421
422     protected void doBegin(Object JavaDoc transaction, TransactionDefinition definition) {
423         HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
424
425         if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
426             throw new IllegalTransactionStateException(
427                     "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
428                     "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
429                     "It is recommended to use a single HibernateTransactionManager for all transactions " +
430                     "on a single DataSource, no matter whether Hibernate or JDBC access.");
431         }
432
433         Session session = null;
434
435         try {
436             if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
437                 Interceptor entityInterceptor = getEntityInterceptor();
438                 Session newSession = (entityInterceptor != null ?
439                         getSessionFactory().openSession(entityInterceptor) : getSessionFactory().openSession());
440                 if (logger.isDebugEnabled()) {
441                     logger.debug("Opened new Session [" + SessionFactoryUtils.toString(newSession) +
442                             "] for Hibernate transaction");
443                 }
444                 txObject.setSessionHolder(new SessionHolder(newSession), true);
445             }
446
447             txObject.getSessionHolder().setSynchronizedWithTransaction(true);
448             session = txObject.getSessionHolder().getSession();
449
450             if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
451                 // We're allowed to change the transaction settings of the JDBC Connection.
452
if (logger.isDebugEnabled()) {
453                     logger.debug(
454                             "Preparing JDBC Connection of Hibernate Session [" + SessionFactoryUtils.toString(session) + "]");
455                 }
456                 Connection JavaDoc con = session.connection();
457                 Integer JavaDoc previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
458                 txObject.setPreviousIsolationLevel(previousIsolationLevel);
459             }
460             else {
461                 // Not allowed to change the transaction settings of the JDBC Connection.
462
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
463                     // We should set a specific isolation level but are not allowed to...
464
throw new InvalidIsolationLevelException(
465                             "HibernateTransactionManager is not allowed to support custom isolation levels: " +
466                             "make sure that its 'prepareConnection' flag is on (the default) and that the " +
467                             "Hibernate connection release mode is set to 'on_close' (LocalSessionFactoryBean's default)");
468                 }
469                 if (logger.isDebugEnabled()) {
470                     logger.debug(
471                             "Not preparing JDBC Connection of Hibernate Session [" + SessionFactoryUtils.toString(session) + "]");
472                 }
473             }
474
475             if (definition.isReadOnly() && txObject.isNewSessionHolder()) {
476                 // Just set to NEVER in case of a new Session for this transaction.
477
session.setFlushMode(FlushMode.NEVER);
478             }
479
480             if (!definition.isReadOnly() && !txObject.isNewSessionHolder()) {
481                 // We need AUTO or COMMIT for a non-read-only transaction.
482
FlushMode flushMode = session.getFlushMode();
483                 if (flushMode.lessThan(FlushMode.COMMIT)) {
484                     session.setFlushMode(FlushMode.AUTO);
485                     txObject.getSessionHolder().setPreviousFlushMode(flushMode);
486                 }
487             }
488
489             Transaction hibTx = null;
490
491             // Register transaction timeout.
492
int timeout = determineTimeout(definition);
493             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
494                 if (hibernateSetTimeoutAvailable) {
495                     // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1
496
// Applies to all statements, also to inserts, updates and deletes!
497
hibTx = session.getTransaction();
498                     hibTx.setTimeout(timeout);
499                     hibTx.begin();
500                 }
501                 else {
502                     // Use Spring query timeouts driven by SessionHolder on Hibernate 3.0
503
// Only applies to Hibernate queries, not to insert/update/delete statements.
504
hibTx = session.beginTransaction();
505                     txObject.getSessionHolder().setTimeoutInSeconds(timeout);
506                 }
507             }
508             else {
509                 // Open a plain Hibernate transaction without specified timeout.
510
hibTx = session.beginTransaction();
511             }
512
513             // Add the Hibernate transaction to the session holder.
514
txObject.getSessionHolder().setTransaction(hibTx);
515
516             // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
517
if (getDataSource() != null) {
518                 Connection JavaDoc con = session.connection();
519                 ConnectionHolder conHolder = new ConnectionHolder(con);
520                 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
521                     conHolder.setTimeoutInSeconds(timeout);
522                 }
523                 if (logger.isDebugEnabled()) {
524                     logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]");
525                 }
526                 TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
527                 txObject.setConnectionHolder(conHolder);
528             }
529
530             // Bind the session holder to the thread.
531
if (txObject.isNewSessionHolder()) {
532                 TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
533             }
534         }
535
536         catch (Exception JavaDoc ex) {
537             SessionFactoryUtils.closeSession(session);
538             throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
539         }
540     }
541
542     protected Object JavaDoc doSuspend(Object JavaDoc transaction) {
543         HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
544         txObject.setSessionHolder(null, false);
545         SessionHolder sessionHolder =
546                 (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
547         txObject.setConnectionHolder(null);
548         ConnectionHolder connectionHolder = null;
549         if (getDataSource() != null) {
550             connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
551         }
552         return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
553     }
554
555     protected void doResume(Object JavaDoc transaction, Object JavaDoc suspendedResources) {
556         SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
557         if (TransactionSynchronizationManager.hasResource(getSessionFactory())) {
558             // From non-transactional code running in active transaction synchronization
559
// -> can be safely removed, will be closed on transaction completion.
560
TransactionSynchronizationManager.unbindResource(getSessionFactory());
561         }
562         TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder());
563         if (getDataSource() != null) {
564             TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
565         }
566     }
567
568     protected void doCommit(DefaultTransactionStatus status) {
569         HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
570         if (status.isDebug()) {
571             logger.debug("Committing Hibernate transaction on Session [" +
572                     SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "]");
573         }
574         try {
575             txObject.getSessionHolder().getTransaction().commit();
576         }
577         catch (org.hibernate.TransactionException ex) {
578             // assumably from commit call to the underlying JDBC connection
579
throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
580         }
581         catch (HibernateException ex) {
582             // assumably failed to flush changes to database
583
throw convertHibernateAccessException(ex);
584         }
585     }
586
587     protected void doRollback(DefaultTransactionStatus status) {
588         HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
589         if (status.isDebug()) {
590             logger.debug("Rolling back Hibernate transaction on Session [" +
591                     SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "]");
592         }
593         try {
594             txObject.getSessionHolder().getTransaction().rollback();
595         }
596         catch (org.hibernate.TransactionException ex) {
597             throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
598         }
599         catch (HibernateException ex) {
600             // Shouldn't really happen, as a rollback doesn't cause a flush.
601
throw convertHibernateAccessException(ex);
602         }
603         finally {
604             if (!txObject.isNewSessionHolder()) {
605                 // Clear all pending inserts/updates/deletes in the Session.
606
// Necessary for pre-bound Sessions, to avoid inconsistent state.
607
txObject.getSessionHolder().getSession().clear();
608             }
609         }
610     }
611
612     protected void doSetRollbackOnly(DefaultTransactionStatus status) {
613         HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
614         if (status.isDebug()) {
615             logger.debug("Setting Hibernate transaction on Session [" +
616                     SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "] rollback-only");
617         }
618         txObject.setRollbackOnly();
619     }
620
621     protected void doCleanupAfterCompletion(Object JavaDoc transaction) {
622         HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
623
624         // Remove the session holder from the thread.
625
if (txObject.isNewSessionHolder()) {
626             TransactionSynchronizationManager.unbindResource(getSessionFactory());
627         }
628
629         // Remove the JDBC connection holder from the thread, if exposed.
630
if (getDataSource() != null) {
631             TransactionSynchronizationManager.unbindResource(getDataSource());
632         }
633
634         Session session = txObject.getSessionHolder().getSession();
635         if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) {
636             // We're running with connection release mode "on_close": We're able to reset
637
// the isolation level and/or read-only flag of the JDBC Connection here.
638
// Else, we need to rely on the connection pool to perform proper cleanup.
639
try {
640                 Connection JavaDoc con = session.connection();
641                 DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
642             }
643             catch (HibernateException ex) {
644                 logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
645             }
646         }
647
648         if (txObject.isNewSessionHolder()) {
649             if (logger.isDebugEnabled()) {
650                 logger.debug("Closing Hibernate Session [" + SessionFactoryUtils.toString(session) +
651                         "] after transaction");
652             }
653             SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
654         }
655         else {
656             if (logger.isDebugEnabled()) {
657                 logger.debug("Not closing pre-bound Hibernate Session [" +
658                         SessionFactoryUtils.toString(session) + "] after transaction");
659             }
660             if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
661                 session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
662             }
663             if (hibernateSetTimeoutAvailable) {
664                 // Running against Hibernate 3.1+...
665
// Let's explicitly disconnect the Session to provide efficient Connection handling
666
// even with connection release mode "on_close". The Session will automatically
667
// obtain a new Connection in case of further database access.
668
// Couldn't do this on Hibernate 3.0, where disconnect required a manual reconnect.
669
session.disconnect();
670             }
671         }
672         txObject.getSessionHolder().clear();
673     }
674
675     /**
676      * Return whether the given Hibernate Session will always hold the same
677      * JDBC Connection. This is used to check whether the transaction manager
678      * can safely prepare and clean up the JDBC Connection used for a transaction.
679      * <p>Default implementation checks the Session's connection release mode
680      * to be "on_close". Unfortunately, this requires casting to SessionImpl,
681      * as of Hibernate 3.0/3.1. If that cast doesn't work, we'll simply assume
682      * we're safe and return <code>true</code>.
683      * @param session the Hibernate Session to check
684      * @see org.hibernate.impl.SessionImpl#getConnectionReleaseMode()
685      * @see org.hibernate.ConnectionReleaseMode#ON_CLOSE
686      */

687     protected boolean isSameConnectionForEntireSession(Session session) {
688         if (!(session instanceof SessionImpl)) {
689             // The best we can do is to assume we're safe.
690
return true;
691         }
692         ConnectionReleaseMode releaseMode = ((SessionImpl) session).getConnectionReleaseMode();
693         return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode);
694     }
695
696
697     /**
698      * Convert the given HibernateException to an appropriate exception
699      * from the <code>org.springframework.dao</code> hierarchy.
700      * <p>Will automatically apply a specified SQLExceptionTranslator to a
701      * Hibernate JDBCException, else rely on Hibernate's default translation.
702      * @param ex HibernateException that occured
703      * @return a corresponding DataAccessException
704      * @see SessionFactoryUtils#convertHibernateAccessException
705      * @see #setJdbcExceptionTranslator
706      */

707     protected DataAccessException convertHibernateAccessException(HibernateException ex) {
708         if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
709             return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
710         }
711         else if (GenericJDBCException.class.equals(ex.getClass())) {
712             return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
713         }
714         return SessionFactoryUtils.convertHibernateAccessException(ex);
715     }
716
717     /**
718      * Convert the given Hibernate JDBCException to an appropriate exception
719      * from the <code>org.springframework.dao</code> hierarchy, using the
720      * given SQLExceptionTranslator.
721      * @param ex Hibernate JDBCException that occured
722      * @param translator the SQLExceptionTranslator to use
723      * @return a corresponding DataAccessException
724      */

725     protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) {
726         return translator.translate("Hibernate flushing: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
727     }
728
729     /**
730      * Obtain a default SQLExceptionTranslator, lazily creating it if necessary.
731      * <p>Creates a default
732      * {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator}
733      * for the SessionFactory's underlying DataSource.
734      */

735     protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
736         if (this.defaultJdbcExceptionTranslator == null) {
737             if (getDataSource() != null) {
738                 this.defaultJdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(getDataSource());
739             }
740             else {
741                 this.defaultJdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory());
742             }
743         }
744         return this.defaultJdbcExceptionTranslator;
745     }
746
747
748     /**
749      * Hibernate transaction object, representing a SessionHolder.
750      * Used as transaction object by HibernateTransactionManager.
751      *
752      * <p>Derives from JdbcTransactionObjectSupport in order to inherit the
753      * capability to manage JDBC 3.0 Savepoints for underlying JDBC Connections.
754      *
755      * @see SessionHolder
756      */

757     private static class HibernateTransactionObject extends JdbcTransactionObjectSupport {
758
759         private SessionHolder sessionHolder;
760
761         private boolean newSessionHolder;
762
763         public void setSessionHolder(SessionHolder sessionHolder, boolean newSessionHolder) {
764             this.sessionHolder = sessionHolder;
765             this.newSessionHolder = newSessionHolder;
766         }
767
768         public SessionHolder getSessionHolder() {
769             return this.sessionHolder;
770         }
771
772         public boolean isNewSessionHolder() {
773             return this.newSessionHolder;
774         }
775
776         public boolean hasTransaction() {
777             return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
778         }
779
780         public void setRollbackOnly() {
781             getSessionHolder().setRollbackOnly();
782             if (hasConnectionHolder()) {
783                 getConnectionHolder().setRollbackOnly();
784             }
785         }
786
787         public boolean isRollbackOnly() {
788             return getSessionHolder().isRollbackOnly() ||
789                     (hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
790         }
791     }
792
793
794     /**
795      * Holder for suspended resources.
796      * Used internally by <code>doSuspend</code> and <code>doResume</code>.
797      */

798     private static class SuspendedResourcesHolder {
799
800         private final SessionHolder sessionHolder;
801
802         private final ConnectionHolder connectionHolder;
803
804         private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
805             this.sessionHolder = sessionHolder;
806             this.connectionHolder = conHolder;
807         }
808
809         private SessionHolder getSessionHolder() {
810             return this.sessionHolder;
811         }
812
813         private ConnectionHolder getConnectionHolder() {
814             return this.connectionHolder;
815         }
816     }
817
818 }
819
Popular Tags