KickJava   Java API By Example, From Geeks To Geeks.

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

130 public class HibernateTransactionManager extends AbstractPlatformTransactionManager
131         implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
132
133     private SessionFactory sessionFactory;
134
135     private DataSource JavaDoc dataSource;
136
137     private boolean autodetectDataSource = true;
138
139     private Object JavaDoc entityInterceptor;
140
141     private SQLExceptionTranslator jdbcExceptionTranslator;
142
143     /**
144      * Just needed for entityInterceptorBeanName.
145      * @see #setEntityInterceptorBeanName
146      */

147     private BeanFactory beanFactory;
148
149
150     /**
151      * Create a new HibernateTransactionManager instance.
152      * A SessionFactory has to be set to be able to use it.
153      * @see #setSessionFactory
154      */

155     public HibernateTransactionManager() {
156     }
157
158     /**
159      * Create a new HibernateTransactionManager instance.
160      * @param sessionFactory SessionFactory to manage transactions for
161      */

162     public HibernateTransactionManager(SessionFactory sessionFactory) {
163         this.sessionFactory = sessionFactory;
164         afterPropertiesSet();
165     }
166
167     /**
168      * Set the SessionFactory that this instance should manage transactions for.
169      */

170     public void setSessionFactory(SessionFactory sessionFactory) {
171         this.sessionFactory = sessionFactory;
172     }
173
174     /**
175      * Return the SessionFactory that this instance should manage transactions for.
176      */

177     public SessionFactory getSessionFactory() {
178         return this.sessionFactory;
179     }
180
181     /**
182      * Set the JDBC DataSource that this instance should manage transactions for.
183      * The DataSource should match the one used by the Hibernate SessionFactory:
184      * for example, you could specify the same JNDI DataSource for both.
185      * <p>If the SessionFactory was configured with LocalDataSourceConnectionProvider,
186      * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource",
187      * the DataSource will be autodetected: You can still explictly specify the
188      * DataSource, but you don't need to in this case.
189      * <p>A transactional JDBC Connection for this DataSource will be provided to
190      * application code accessing this DataSource directly via DataSourceUtils
191      * or JdbcTemplate. The Connection will be taken from the Hibernate Session.
192      * <p>The DataSource specified here should be the target DataSource to manage
193      * transactions for, not a TransactionAwareDataSourceProxy. Only data access
194      * code may work with TransactionAwareDataSourceProxy, while the transaction
195      * manager needs to work on the underlying target DataSource. If there's
196      * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
197      * unwrapped to extract its target DataSource.
198      * @see #setAutodetectDataSource
199      * @see LocalDataSourceConnectionProvider
200      * @see LocalSessionFactoryBean#setDataSource
201      * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
202      * @see org.springframework.jdbc.datasource.DataSourceUtils
203      * @see org.springframework.jdbc.core.JdbcTemplate
204      */

205     public void setDataSource(DataSource JavaDoc dataSource) {
206         if (dataSource instanceof TransactionAwareDataSourceProxy) {
207             // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
208
// for its underlying target DataSource, else data access code won't see
209
// properly exposed transactions (i.e. transactions for the target DataSource).
210
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
211         }
212         else {
213             this.dataSource = dataSource;
214         }
215     }
216
217     /**
218      * Return the JDBC DataSource that this instance manages transactions for.
219      */

220     public DataSource JavaDoc getDataSource() {
221         return this.dataSource;
222     }
223
224     /**
225      * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory,
226      * if set via LocalSessionFactoryBean's <code>setDataSource</code>. Default is "true".
227      * <p>Can be turned off to deliberately ignore an available DataSource,
228      * to not expose Hibernate transactions as JDBC transactions for that DataSource.
229      * @see #setDataSource
230      * @see LocalSessionFactoryBean#setDataSource
231      */

232     public void setAutodetectDataSource(boolean autodetectDataSource) {
233         this.autodetectDataSource = autodetectDataSource;
234     }
235
236     /**
237      * Set the bean name of a Hibernate entity interceptor that allows to inspect
238      * and change property values before writing to and reading from the database.
239      * Will get applied to any new Session created by this transaction manager.
240      * <p>Requires the bean factory to be known, to be able to resolve the bean
241      * name to an interceptor instance on session creation. Typically used for
242      * prototype interceptors, i.e. a new interceptor instance per session.
243      * <p>Can also be used for shared interceptor instances, but it is recommended
244      * to set the interceptor reference directly in such a scenario.
245      * @param entityInterceptorBeanName the name of the entity interceptor in
246      * the bean factory
247      * @see #setBeanFactory
248      * @see #setEntityInterceptor
249      */

250     public void setEntityInterceptorBeanName(String JavaDoc entityInterceptorBeanName) {
251         this.entityInterceptor = entityInterceptorBeanName;
252     }
253
254     /**
255      * Set a Hibernate entity interceptor that allows to inspect and change
256      * property values before writing to and reading from the database.
257      * Will get applied to any new Session created by this transaction manager.
258      * <p>Such an interceptor can either be set at the SessionFactory level,
259      * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
260      * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
261      * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
262      * to avoid repeated configuration and guarantee consistent behavior in transactions.
263      * @see LocalSessionFactoryBean#setEntityInterceptor
264      * @see HibernateTemplate#setEntityInterceptor
265      * @see HibernateInterceptor#setEntityInterceptor
266      */

267     public void setEntityInterceptor(Interceptor entityInterceptor) {
268         this.entityInterceptor = entityInterceptor;
269     }
270
271     /**
272      * Return the current Hibernate entity interceptor, or <code>null</code> if none.
273      * Resolves an entity interceptor bean name via the bean factory,
274      * if necessary.
275      * @throws IllegalStateException if bean name specified but no bean factory set
276      * @throws BeansException if bean name resolution via the bean factory failed
277      * @see #setEntityInterceptor
278      * @see #setEntityInterceptorBeanName
279      * @see #setBeanFactory
280      */

281     public Interceptor getEntityInterceptor() throws IllegalStateException JavaDoc, BeansException {
282         if (this.entityInterceptor instanceof Interceptor) {
283             return (Interceptor) entityInterceptor;
284         }
285         else if (this.entityInterceptor instanceof String JavaDoc) {
286             if (this.beanFactory == null) {
287                 throw new IllegalStateException JavaDoc("Cannot get entity interceptor via bean name if no bean factory set");
288             }
289             String JavaDoc beanName = (String JavaDoc) this.entityInterceptor;
290             return (Interceptor) this.beanFactory.getBean(beanName, Interceptor.class);
291         }
292         else {
293             return null;
294         }
295     }
296
297     /**
298      * Set the JDBC exception translator for this transaction manager.
299      * Applied to SQLExceptions (wrapped by Hibernate's JDBCException)
300      * thrown by flushing on commit.
301      * <p>The default exception translator is either a SQLErrorCodeSQLExceptionTranslator
302      * if a DataSource is available, or a SQLStateSQLExceptionTranslator else.
303      * @param jdbcExceptionTranslator the exception translator
304      * @see java.sql.SQLException
305      * @see net.sf.hibernate.JDBCException
306      * @see SessionFactoryUtils#newJdbcExceptionTranslator
307      * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
308      * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
309      */

310     public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
311         this.jdbcExceptionTranslator = jdbcExceptionTranslator;
312     }
313
314     /**
315      * Return the JDBC exception translator for this transaction manager.
316      * <p>Creates a default SQLErrorCodeSQLExceptionTranslator or SQLStateSQLExceptionTranslator
317      * for the specified SessionFactory, if no exception translator explicitly specified.
318      * @see #setJdbcExceptionTranslator
319      */

320     public synchronized SQLExceptionTranslator getJdbcExceptionTranslator() {
321         if (this.jdbcExceptionTranslator == null) {
322             if (getDataSource() != null) {
323                 this.jdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(getDataSource());
324             }
325             else {
326                 this.jdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory());
327             }
328         }
329         return this.jdbcExceptionTranslator;
330     }
331
332     /**
333      * The bean factory just needs to be known for resolving entity interceptor
334      * bean names. It does not need to be set for any other mode of operation.
335      * @see #setEntityInterceptorBeanName
336      */

337     public void setBeanFactory(BeanFactory beanFactory) {
338         this.beanFactory = beanFactory;
339     }
340
341     public void afterPropertiesSet() {
342         if (getSessionFactory() == null) {
343             throw new IllegalArgumentException JavaDoc("Property 'sessionFactory' is required");
344         }
345         if (this.entityInterceptor instanceof String JavaDoc && this.beanFactory == null) {
346             throw new IllegalArgumentException JavaDoc("'beanFactory' is required for 'entityInterceptorBeanName'");
347         }
348
349         // Check for SessionFactory's DataSource.
350
if (this.autodetectDataSource && getDataSource() == null) {
351             DataSource JavaDoc sfds = SessionFactoryUtils.getDataSource(getSessionFactory());
352             if (sfds != null) {
353                 // Use the SessionFactory's DataSource for exposing transactions to JDBC code.
354
if (logger.isInfoEnabled()) {
355                     logger.info("Using DataSource [" + sfds +
356                             "] of Hibernate SessionFactory for HibernateTransactionManager");
357                 }
358                 setDataSource(sfds);
359             }
360         }
361     }
362
363
364     public Object JavaDoc getResourceFactory() {
365         return getSessionFactory();
366     }
367
368     protected Object JavaDoc doGetTransaction() {
369         HibernateTransactionObject txObject = new HibernateTransactionObject();
370         txObject.setSavepointAllowed(isNestedTransactionAllowed());
371
372         SessionHolder sessionHolder =
373                 (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
374         if (sessionHolder != null) {
375             if (logger.isDebugEnabled()) {
376                 logger.debug("Found thread-bound Session [" + sessionHolder.getSession() +
377                         "] for Hibernate transaction");
378             }
379             txObject.setSessionHolder(sessionHolder, false);
380         }
381
382         if (getDataSource() != null) {
383             ConnectionHolder conHolder = (ConnectionHolder)
384                     TransactionSynchronizationManager.getResource(getDataSource());
385             txObject.setConnectionHolder(conHolder);
386         }
387
388         return txObject;
389     }
390
391     protected boolean isExistingTransaction(Object JavaDoc transaction) {
392         return ((HibernateTransactionObject) transaction).hasTransaction();
393     }
394
395     protected void doBegin(Object JavaDoc transaction, TransactionDefinition definition) {
396         HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
397
398         if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
399             throw new IllegalTransactionStateException(
400                     "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
401                     "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
402                     "It is recommended to use a single HibernateTransactionManager for all transactions " +
403                     "on a single DataSource, no matter whether Hibernate or JDBC access.");
404         }
405
406         Session session = null;
407
408         try {
409             if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
410                 Interceptor entityInterceptor = getEntityInterceptor();
411                 Session newSession = (entityInterceptor != null ?
412                         getSessionFactory().openSession(entityInterceptor) : getSessionFactory().openSession());
413                 if (logger.isDebugEnabled()) {
414                     logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
415                 }
416                 txObject.setSessionHolder(new SessionHolder(newSession), true);
417             }
418
419             txObject.getSessionHolder().setSynchronizedWithTransaction(true);
420             session = txObject.getSessionHolder().getSession();
421
422             Connection JavaDoc con = session.connection();
423             Integer JavaDoc previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
424             txObject.setPreviousIsolationLevel(previousIsolationLevel);
425
426             if (definition.isReadOnly() && txObject.isNewSessionHolder()) {
427                 // Just set to NEVER in case of a new Session for this transaction.
428
session.setFlushMode(FlushMode.NEVER);
429             }
430
431             if (!definition.isReadOnly() && !txObject.isNewSessionHolder()) {
432                 // We need AUTO or COMMIT for a non-read-only transaction.
433
FlushMode flushMode = session.getFlushMode();
434                 if (FlushMode.NEVER.equals(flushMode)) {
435                     session.setFlushMode(FlushMode.AUTO);
436                     txObject.getSessionHolder().setPreviousFlushMode(flushMode);
437                 }
438             }
439
440             // Add the Hibernate transaction to the session holder.
441
txObject.getSessionHolder().setTransaction(session.beginTransaction());
442
443             // Register transaction timeout.
444
int timeout = determineTimeout(definition);
445             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
446                 txObject.getSessionHolder().setTimeoutInSeconds(timeout);
447             }
448
449             // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
450
if (getDataSource() != null) {
451                 ConnectionHolder conHolder = new ConnectionHolder(con);
452                 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
453                     conHolder.setTimeoutInSeconds(timeout);
454                 }
455                 if (logger.isDebugEnabled()) {
456                     logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]");
457                 }
458                 TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
459                 txObject.setConnectionHolder(conHolder);
460             }
461
462             // Bind the session holder to the thread.
463
if (txObject.isNewSessionHolder()) {
464                 TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
465             }
466         }
467
468         catch (Exception JavaDoc ex) {
469             SessionFactoryUtils.closeSession(session);
470             throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
471         }
472     }
473
474     protected Object JavaDoc doSuspend(Object JavaDoc transaction) {
475         HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
476         txObject.setSessionHolder(null, false);
477         SessionHolder sessionHolder =
478                 (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
479         txObject.setConnectionHolder(null);
480         ConnectionHolder connectionHolder = null;
481         if (getDataSource() != null) {
482             connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
483         }
484         return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
485     }
486
487     protected void doResume(Object JavaDoc transaction, Object JavaDoc suspendedResources) {
488         SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
489         if (TransactionSynchronizationManager.hasResource(getSessionFactory())) {
490             // From non-transactional code running in active transaction synchronization
491
// -> can be safely removed, will be closed on transaction completion.
492
TransactionSynchronizationManager.unbindResource(getSessionFactory());
493         }
494         TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder());
495         if (getDataSource() != null) {
496             TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
497         }
498     }
499
500     protected void doCommit(DefaultTransactionStatus status) {
501         HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
502         if (status.isDebug()) {
503             logger.debug("Committing Hibernate transaction on Session [" +
504                     txObject.getSessionHolder().getSession() + "]");
505         }
506         try {
507             txObject.getSessionHolder().getTransaction().commit();
508         }
509         catch (net.sf.hibernate.TransactionException ex) {
510             // assumably from commit call to the underlying JDBC connection
511
throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
512         }
513         catch (HibernateException ex) {
514             // assumably failed to flush changes to database
515
throw convertHibernateAccessException(ex);
516         }
517     }
518
519     protected void doRollback(DefaultTransactionStatus status) {
520         HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
521         if (status.isDebug()) {
522             logger.debug("Rolling back Hibernate transaction on Session [" +
523                     txObject.getSessionHolder().getSession() + "]");
524         }
525         try {
526             txObject.getSessionHolder().getTransaction().rollback();
527         }
528         catch (net.sf.hibernate.TransactionException ex) {
529             throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
530         }
531         catch (HibernateException ex) {
532             // Shouldn't really happen, as a rollback doesn't cause a flush.
533
throw convertHibernateAccessException(ex);
534         }
535         finally {
536             if (!txObject.isNewSessionHolder()) {
537                 // Clear all pending inserts/updates/deletes in the Session.
538
// Necessary for pre-bound Sessions, to avoid inconsistent state.
539
txObject.getSessionHolder().getSession().clear();
540             }
541         }
542     }
543
544     protected void doSetRollbackOnly(DefaultTransactionStatus status) {
545         HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
546         if (status.isDebug()) {
547             logger.debug("Setting Hibernate transaction on Session [" +
548                     txObject.getSessionHolder().getSession() + "] rollback-only");
549         }
550         txObject.setRollbackOnly();
551     }
552
553     protected void doCleanupAfterCompletion(Object JavaDoc transaction) {
554         HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
555
556         // Remove the session holder from the thread.
557
if (txObject.isNewSessionHolder()) {
558             TransactionSynchronizationManager.unbindResource(getSessionFactory());
559         }
560
561         // Remove the JDBC connection holder from the thread, if exposed.
562
if (getDataSource() != null) {
563             TransactionSynchronizationManager.unbindResource(getDataSource());
564         }
565
566         try {
567             Connection JavaDoc con = txObject.getSessionHolder().getSession().connection();
568             DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
569         }
570         catch (HibernateException ex) {
571             logger.info("Could not access JDBC Connection of Hibernate Session", ex);
572         }
573
574         Session session = txObject.getSessionHolder().getSession();
575         if (txObject.isNewSessionHolder()) {
576             if (logger.isDebugEnabled()) {
577                 logger.debug("Closing Hibernate Session [" + session + "] after transaction");
578             }
579             SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
580         }
581         else {
582             if (logger.isDebugEnabled()) {
583                 logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction");
584             }
585             if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
586                 session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
587             }
588         }
589         txObject.getSessionHolder().clear();
590     }
591
592
593     /**
594      * Convert the given HibernateException to an appropriate exception from
595      * the org.springframework.dao hierarchy. Can be overridden in subclasses.
596      * @param ex HibernateException that occured
597      * @return the corresponding DataAccessException instance
598      * @see #convertJdbcAccessException(net.sf.hibernate.JDBCException)
599      */

600     protected DataAccessException convertHibernateAccessException(HibernateException ex) {
601         if (ex instanceof JDBCException) {
602             return convertJdbcAccessException((JDBCException) ex);
603         }
604         return SessionFactoryUtils.convertHibernateAccessException(ex);
605     }
606
607     /**
608      * Convert the given JDBCException to an appropriate exception from the
609      * <code>org.springframework.dao</code> hierarchy.
610      * Uses a JDBC exception translator. Can be overridden in subclasses.
611      * @param ex JDBCException that occured, wrapping a SQLException
612      * @return the corresponding DataAccessException instance
613      * @see #setJdbcExceptionTranslator
614      */

615     protected DataAccessException convertJdbcAccessException(JDBCException ex) {
616         return getJdbcExceptionTranslator().translate(
617                 "Hibernate operation: " + ex.getMessage(), null, ex.getSQLException());
618     }
619
620
621     /**
622      * Hibernate transaction object, representing a SessionHolder.
623      * Used as transaction object by HibernateTransactionManager.
624      *
625      * <p>Derives from JdbcTransactionObjectSupport in order to inherit the
626      * capability to manage JDBC 3.0 Savepoints for underlying JDBC Connections.
627      *
628      * @see SessionHolder
629      */

630     private static class HibernateTransactionObject extends JdbcTransactionObjectSupport {
631
632         private SessionHolder sessionHolder;
633
634         private boolean newSessionHolder;
635
636         public void setSessionHolder(SessionHolder sessionHolder, boolean newSessionHolder) {
637             this.sessionHolder = sessionHolder;
638             this.newSessionHolder = newSessionHolder;
639         }
640
641         public SessionHolder getSessionHolder() {
642             return this.sessionHolder;
643         }
644
645         public boolean isNewSessionHolder() {
646             return this.newSessionHolder;
647         }
648
649         public boolean hasTransaction() {
650             return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
651         }
652
653         public void setRollbackOnly() {
654             getSessionHolder().setRollbackOnly();
655             if (hasConnectionHolder()) {
656                 getConnectionHolder().setRollbackOnly();
657             }
658         }
659
660         public boolean isRollbackOnly() {
661             return getSessionHolder().isRollbackOnly() ||
662                     (hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
663         }
664     }
665
666
667     /**
668      * Holder for suspended resources.
669      * Used internally by <code>doSuspend</code> and <code>doResume</code>.
670      */

671     private static class SuspendedResourcesHolder {
672
673         private final SessionHolder sessionHolder;
674
675         private final ConnectionHolder connectionHolder;
676
677         private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
678             this.sessionHolder = sessionHolder;
679             this.connectionHolder = conHolder;
680         }
681
682         private SessionHolder getSessionHolder() {
683             return this.sessionHolder;
684         }
685
686         private ConnectionHolder getConnectionHolder() {
687             return this.connectionHolder;
688         }
689     }
690
691 }
692
Popular Tags