001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.orm.hibernate;
018:
019: import java.sql.Connection;
020:
021: import javax.sql.DataSource;
022:
023: import net.sf.hibernate.FlushMode;
024: import net.sf.hibernate.HibernateException;
025: import net.sf.hibernate.Interceptor;
026: import net.sf.hibernate.JDBCException;
027: import net.sf.hibernate.Session;
028: import net.sf.hibernate.SessionFactory;
029:
030: import org.springframework.beans.BeansException;
031: import org.springframework.beans.factory.BeanFactory;
032: import org.springframework.beans.factory.BeanFactoryAware;
033: import org.springframework.beans.factory.InitializingBean;
034: import org.springframework.dao.DataAccessException;
035: import org.springframework.jdbc.datasource.ConnectionHolder;
036: import org.springframework.jdbc.datasource.DataSourceUtils;
037: import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
038: import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
039: import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
040: import org.springframework.jdbc.support.SQLExceptionTranslator;
041: import org.springframework.transaction.CannotCreateTransactionException;
042: import org.springframework.transaction.IllegalTransactionStateException;
043: import org.springframework.transaction.TransactionDefinition;
044: import org.springframework.transaction.TransactionSystemException;
045: import org.springframework.transaction.support.AbstractPlatformTransactionManager;
046: import org.springframework.transaction.support.DefaultTransactionStatus;
047: import org.springframework.transaction.support.ResourceTransactionManager;
048: import org.springframework.transaction.support.TransactionSynchronizationManager;
049:
050: /**
051: * {@link org.springframework.transaction.PlatformTransactionManager}
052: * implementation for a single Hibernate {@link net.sf.hibernate.SessionFactory}.
053: * Binds a Hibernate Session from the specified factory to the thread, potentially
054: * allowing for one thread-bound Session per factory. {@link SessionFactoryUtils}
055: * and {@link HibernateTemplate} are aware of thread-bound Sessions and participate
056: * in such transactions automatically. Using either of those is required for
057: * Hibernate access code that needs to support this transaction handling mechanism.
058: *
059: * <p>Supports custom isolation levels, and timeouts that get applied as appropriate
060: * Hibernate query timeouts. To support the latter, application code must either use
061: * {@link HibernateTemplate} (which by default applies the timeouts) or call
062: * {@link SessionFactoryUtils#applyTransactionTimeout} for each created
063: * Hibernate {@link net.sf.hibernate.Query} object.
064: *
065: * <p>This transaction manager is appropriate for applications that use a single
066: * Hibernate SessionFactory for transactional data access, but it also supports
067: * direct DataSource access within a transaction (i.e. plain JDBC code working
068: * with the same DataSource). This allows for mixing services which access Hibernate
069: * and services which use plain JDBC (without being aware of Hibernate)!
070: * Application code needs to stick to the same simple Connection lookup pattern as
071: * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
072: * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
073: * or going through a
074: * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
075: *
076: * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
077: * this instance needs to be aware of the DataSource ({@link #setDataSource}).
078: * The given DataSource should obviously match the one used by the given
079: * SessionFactory. To achieve this, configure both to the same JNDI DataSource,
080: * or preferably create the SessionFactory with {@link LocalSessionFactoryBean} and
081: * a local DataSource (which will be autodetected by this transaction manager).
082: *
083: * <p>JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager})
084: * is necessary for accessing multiple transactional resources within the same
085: * transaction. The DataSource that Hibernate uses needs to be JTA-enabled in
086: * such a scenario (see container setup). Normally, JTA setup for Hibernate is
087: * somewhat container-specific due to the JTA TransactionManager lookup, required
088: * for proper transactional handling of the SessionFactory-level read-write cache.
089: *
090: * <p>Fortunately, there is an easier way with Spring: {@link SessionFactoryUtils}
091: * (and thus {@link HibernateTemplate}) registers synchronizations with Spring's
092: * {@link org.springframework.transaction.support.TransactionSynchronizationManager}
093: * (as used by {@link org.springframework.transaction.jta.JtaTransactionManager}),
094: * for proper after-completion callbacks. Therefore, as long as Spring's
095: * JtaTransactionManager drives the JTA transactions, Hibernate does not require
096: * any special configuration for proper JTA participation. Note that there are
097: * special restrictions with EJB CMT and restrictive JTA subsystems: See
098: * {@link org.springframework.transaction.jta.JtaTransactionManager}'s javadoc for details.
099: *
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
131: AbstractPlatformTransactionManager implements
132: ResourceTransactionManager, BeanFactoryAware, InitializingBean {
133:
134: private SessionFactory sessionFactory;
135:
136: private DataSource dataSource;
137:
138: private boolean autodetectDataSource = true;
139:
140: private Object entityInterceptor;
141:
142: private SQLExceptionTranslator jdbcExceptionTranslator;
143:
144: /**
145: * Just needed for entityInterceptorBeanName.
146: * @see #setEntityInterceptorBeanName
147: */
148: private BeanFactory beanFactory;
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 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)
211: .getTargetDataSource();
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 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(
251: String entityInterceptorBeanName) {
252: this .entityInterceptor = entityInterceptorBeanName;
253: }
254:
255: /**
256: * Set a Hibernate entity interceptor that allows to inspect and change
257: * property values before writing to and reading from the database.
258: * Will get applied to any new Session created by this transaction manager.
259: * <p>Such an interceptor can either be set at the SessionFactory level,
260: * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
261: * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
262: * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
263: * to avoid repeated configuration and guarantee consistent behavior in transactions.
264: * @see LocalSessionFactoryBean#setEntityInterceptor
265: * @see HibernateTemplate#setEntityInterceptor
266: * @see HibernateInterceptor#setEntityInterceptor
267: */
268: public void setEntityInterceptor(Interceptor entityInterceptor) {
269: this .entityInterceptor = entityInterceptor;
270: }
271:
272: /**
273: * Return the current Hibernate entity interceptor, or <code>null</code> if none.
274: * Resolves an entity interceptor bean name via the bean factory,
275: * if necessary.
276: * @throws IllegalStateException if bean name specified but no bean factory set
277: * @throws BeansException if bean name resolution via the bean factory failed
278: * @see #setEntityInterceptor
279: * @see #setEntityInterceptorBeanName
280: * @see #setBeanFactory
281: */
282: public Interceptor getEntityInterceptor()
283: throws IllegalStateException, BeansException {
284: if (this .entityInterceptor instanceof Interceptor) {
285: return (Interceptor) entityInterceptor;
286: } else if (this .entityInterceptor instanceof String) {
287: if (this .beanFactory == null) {
288: throw new IllegalStateException(
289: "Cannot get entity interceptor via bean name if no bean factory set");
290: }
291: String beanName = (String) this .entityInterceptor;
292: return (Interceptor) this .beanFactory.getBean(beanName,
293: Interceptor.class);
294: } else {
295: return null;
296: }
297: }
298:
299: /**
300: * Set the JDBC exception translator for this transaction manager.
301: * Applied to SQLExceptions (wrapped by Hibernate's JDBCException)
302: * thrown by flushing on commit.
303: * <p>The default exception translator is either a SQLErrorCodeSQLExceptionTranslator
304: * if a DataSource is available, or a SQLStateSQLExceptionTranslator else.
305: * @param jdbcExceptionTranslator the exception translator
306: * @see java.sql.SQLException
307: * @see net.sf.hibernate.JDBCException
308: * @see SessionFactoryUtils#newJdbcExceptionTranslator
309: * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
310: * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
311: */
312: public void setJdbcExceptionTranslator(
313: SQLExceptionTranslator jdbcExceptionTranslator) {
314: this .jdbcExceptionTranslator = jdbcExceptionTranslator;
315: }
316:
317: /**
318: * Return the JDBC exception translator for this transaction manager.
319: * <p>Creates a default SQLErrorCodeSQLExceptionTranslator or SQLStateSQLExceptionTranslator
320: * for the specified SessionFactory, if no exception translator explicitly specified.
321: * @see #setJdbcExceptionTranslator
322: */
323: public synchronized SQLExceptionTranslator getJdbcExceptionTranslator() {
324: if (this .jdbcExceptionTranslator == null) {
325: if (getDataSource() != null) {
326: this .jdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(
327: getDataSource());
328: } else {
329: this .jdbcExceptionTranslator = SessionFactoryUtils
330: .newJdbcExceptionTranslator(getSessionFactory());
331: }
332: }
333: return this .jdbcExceptionTranslator;
334: }
335:
336: /**
337: * The bean factory just needs to be known for resolving entity interceptor
338: * bean names. It does not need to be set for any other mode of operation.
339: * @see #setEntityInterceptorBeanName
340: */
341: public void setBeanFactory(BeanFactory beanFactory) {
342: this .beanFactory = beanFactory;
343: }
344:
345: public void afterPropertiesSet() {
346: if (getSessionFactory() == null) {
347: throw new IllegalArgumentException(
348: "Property 'sessionFactory' is required");
349: }
350: if (this .entityInterceptor instanceof String
351: && this .beanFactory == null) {
352: throw new IllegalArgumentException(
353: "'beanFactory' is required for 'entityInterceptorBeanName'");
354: }
355:
356: // Check for SessionFactory's DataSource.
357: if (this .autodetectDataSource && getDataSource() == null) {
358: DataSource sfds = SessionFactoryUtils
359: .getDataSource(getSessionFactory());
360: if (sfds != null) {
361: // Use the SessionFactory's DataSource for exposing transactions to JDBC code.
362: if (logger.isInfoEnabled()) {
363: logger
364: .info("Using DataSource ["
365: + sfds
366: + "] of Hibernate SessionFactory for HibernateTransactionManager");
367: }
368: setDataSource(sfds);
369: }
370: }
371: }
372:
373: public Object getResourceFactory() {
374: return getSessionFactory();
375: }
376:
377: protected Object doGetTransaction() {
378: HibernateTransactionObject txObject = new HibernateTransactionObject();
379: txObject.setSavepointAllowed(isNestedTransactionAllowed());
380:
381: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
382: .getResource(getSessionFactory());
383: if (sessionHolder != null) {
384: if (logger.isDebugEnabled()) {
385: logger.debug("Found thread-bound Session ["
386: + sessionHolder.getSession()
387: + "] for Hibernate transaction");
388: }
389: txObject.setSessionHolder(sessionHolder, false);
390: }
391:
392: if (getDataSource() != null) {
393: ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager
394: .getResource(getDataSource());
395: txObject.setConnectionHolder(conHolder);
396: }
397:
398: return txObject;
399: }
400:
401: protected boolean isExistingTransaction(Object transaction) {
402: return ((HibernateTransactionObject) transaction)
403: .hasTransaction();
404: }
405:
406: protected void doBegin(Object transaction,
407: TransactionDefinition definition) {
408: HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
409:
410: if (txObject.hasConnectionHolder()
411: && !txObject.getConnectionHolder()
412: .isSynchronizedWithTransaction()) {
413: throw new IllegalTransactionStateException(
414: "Pre-bound JDBC Connection found! HibernateTransactionManager does not support "
415: + "running within DataSourceTransactionManager if told to manage the DataSource itself. "
416: + "It is recommended to use a single HibernateTransactionManager for all transactions "
417: + "on a single DataSource, no matter whether Hibernate or JDBC access.");
418: }
419:
420: Session session = null;
421:
422: try {
423: if (txObject.getSessionHolder() == null
424: || txObject.getSessionHolder()
425: .isSynchronizedWithTransaction()) {
426: Interceptor entityInterceptor = getEntityInterceptor();
427: Session newSession = (entityInterceptor != null ? getSessionFactory()
428: .openSession(entityInterceptor)
429: : getSessionFactory().openSession());
430: if (logger.isDebugEnabled()) {
431: logger.debug("Opened new Session [" + newSession
432: + "] for Hibernate transaction");
433: }
434: txObject.setSessionHolder(
435: new SessionHolder(newSession), true);
436: }
437:
438: txObject.getSessionHolder().setSynchronizedWithTransaction(
439: true);
440: session = txObject.getSessionHolder().getSession();
441:
442: Connection con = session.connection();
443: Integer previousIsolationLevel = DataSourceUtils
444: .prepareConnectionForTransaction(con, definition);
445: txObject.setPreviousIsolationLevel(previousIsolationLevel);
446:
447: if (definition.isReadOnly()
448: && txObject.isNewSessionHolder()) {
449: // Just set to NEVER in case of a new Session for this transaction.
450: session.setFlushMode(FlushMode.NEVER);
451: }
452:
453: if (!definition.isReadOnly()
454: && !txObject.isNewSessionHolder()) {
455: // We need AUTO or COMMIT for a non-read-only transaction.
456: FlushMode flushMode = session.getFlushMode();
457: if (FlushMode.NEVER.equals(flushMode)) {
458: session.setFlushMode(FlushMode.AUTO);
459: txObject.getSessionHolder().setPreviousFlushMode(
460: flushMode);
461: }
462: }
463:
464: // Add the Hibernate transaction to the session holder.
465: txObject.getSessionHolder().setTransaction(
466: session.beginTransaction());
467:
468: // Register transaction timeout.
469: int timeout = determineTimeout(definition);
470: if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
471: txObject.getSessionHolder()
472: .setTimeoutInSeconds(timeout);
473: }
474:
475: // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
476: if (getDataSource() != null) {
477: ConnectionHolder conHolder = new ConnectionHolder(con);
478: if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
479: conHolder.setTimeoutInSeconds(timeout);
480: }
481: if (logger.isDebugEnabled()) {
482: logger
483: .debug("Exposing Hibernate transaction as JDBC transaction ["
484: + con + "]");
485: }
486: TransactionSynchronizationManager.bindResource(
487: getDataSource(), conHolder);
488: txObject.setConnectionHolder(conHolder);
489: }
490:
491: // Bind the session holder to the thread.
492: if (txObject.isNewSessionHolder()) {
493: TransactionSynchronizationManager.bindResource(
494: getSessionFactory(), txObject
495: .getSessionHolder());
496: }
497: }
498:
499: catch (Exception ex) {
500: SessionFactoryUtils.closeSession(session);
501: throw new CannotCreateTransactionException(
502: "Could not open Hibernate Session for transaction",
503: ex);
504: }
505: }
506:
507: protected Object doSuspend(Object transaction) {
508: HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
509: txObject.setSessionHolder(null, false);
510: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
511: .unbindResource(getSessionFactory());
512: txObject.setConnectionHolder(null);
513: ConnectionHolder connectionHolder = null;
514: if (getDataSource() != null) {
515: connectionHolder = (ConnectionHolder) TransactionSynchronizationManager
516: .unbindResource(getDataSource());
517: }
518: return new SuspendedResourcesHolder(sessionHolder,
519: connectionHolder);
520: }
521:
522: protected void doResume(Object transaction,
523: Object suspendedResources) {
524: SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
525: if (TransactionSynchronizationManager
526: .hasResource(getSessionFactory())) {
527: // From non-transactional code running in active transaction synchronization
528: // -> can be safely removed, will be closed on transaction completion.
529: TransactionSynchronizationManager
530: .unbindResource(getSessionFactory());
531: }
532: TransactionSynchronizationManager
533: .bindResource(getSessionFactory(), resourcesHolder
534: .getSessionHolder());
535: if (getDataSource() != null) {
536: TransactionSynchronizationManager.bindResource(
537: getDataSource(), resourcesHolder
538: .getConnectionHolder());
539: }
540: }
541:
542: protected void doCommit(DefaultTransactionStatus status) {
543: HibernateTransactionObject txObject = (HibernateTransactionObject) status
544: .getTransaction();
545: if (status.isDebug()) {
546: logger
547: .debug("Committing Hibernate transaction on Session ["
548: + txObject.getSessionHolder().getSession()
549: + "]");
550: }
551: try {
552: txObject.getSessionHolder().getTransaction().commit();
553: } catch (net.sf.hibernate.TransactionException ex) {
554: // assumably from commit call to the underlying JDBC connection
555: throw new TransactionSystemException(
556: "Could not commit Hibernate transaction", ex);
557: } catch (HibernateException ex) {
558: // assumably failed to flush changes to database
559: throw convertHibernateAccessException(ex);
560: }
561: }
562:
563: protected void doRollback(DefaultTransactionStatus status) {
564: HibernateTransactionObject txObject = (HibernateTransactionObject) status
565: .getTransaction();
566: if (status.isDebug()) {
567: logger
568: .debug("Rolling back Hibernate transaction on Session ["
569: + txObject.getSessionHolder().getSession()
570: + "]");
571: }
572: try {
573: txObject.getSessionHolder().getTransaction().rollback();
574: } catch (net.sf.hibernate.TransactionException ex) {
575: throw new TransactionSystemException(
576: "Could not roll back Hibernate transaction", ex);
577: } catch (HibernateException ex) {
578: // Shouldn't really happen, as a rollback doesn't cause a flush.
579: throw convertHibernateAccessException(ex);
580: } finally {
581: if (!txObject.isNewSessionHolder()) {
582: // Clear all pending inserts/updates/deletes in the Session.
583: // Necessary for pre-bound Sessions, to avoid inconsistent state.
584: txObject.getSessionHolder().getSession().clear();
585: }
586: }
587: }
588:
589: protected void doSetRollbackOnly(DefaultTransactionStatus status) {
590: HibernateTransactionObject txObject = (HibernateTransactionObject) status
591: .getTransaction();
592: if (status.isDebug()) {
593: logger.debug("Setting Hibernate transaction on Session ["
594: + txObject.getSessionHolder().getSession()
595: + "] rollback-only");
596: }
597: txObject.setRollbackOnly();
598: }
599:
600: protected void doCleanupAfterCompletion(Object transaction) {
601: HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
602:
603: // Remove the session holder from the thread.
604: if (txObject.isNewSessionHolder()) {
605: TransactionSynchronizationManager
606: .unbindResource(getSessionFactory());
607: }
608:
609: // Remove the JDBC connection holder from the thread, if exposed.
610: if (getDataSource() != null) {
611: TransactionSynchronizationManager
612: .unbindResource(getDataSource());
613: }
614:
615: try {
616: Connection con = txObject.getSessionHolder().getSession()
617: .connection();
618: DataSourceUtils.resetConnectionAfterTransaction(con,
619: txObject.getPreviousIsolationLevel());
620: } catch (HibernateException ex) {
621: logger
622: .info(
623: "Could not access JDBC Connection of Hibernate Session",
624: ex);
625: }
626:
627: Session session = txObject.getSessionHolder().getSession();
628: if (txObject.isNewSessionHolder()) {
629: if (logger.isDebugEnabled()) {
630: logger.debug("Closing Hibernate Session [" + session
631: + "] after transaction");
632: }
633: SessionFactoryUtils.closeSessionOrRegisterDeferredClose(
634: session, getSessionFactory());
635: } else {
636: if (logger.isDebugEnabled()) {
637: logger
638: .debug("Not closing pre-bound Hibernate Session ["
639: + session + "] after transaction");
640: }
641: if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
642: session.setFlushMode(txObject.getSessionHolder()
643: .getPreviousFlushMode());
644: }
645: }
646: txObject.getSessionHolder().clear();
647: }
648:
649: /**
650: * Convert the given HibernateException to an appropriate exception from
651: * the org.springframework.dao hierarchy. Can be overridden in subclasses.
652: * @param ex HibernateException that occured
653: * @return the corresponding DataAccessException instance
654: * @see #convertJdbcAccessException(net.sf.hibernate.JDBCException)
655: */
656: protected DataAccessException convertHibernateAccessException(
657: HibernateException ex) {
658: if (ex instanceof JDBCException) {
659: return convertJdbcAccessException((JDBCException) ex);
660: }
661: return SessionFactoryUtils.convertHibernateAccessException(ex);
662: }
663:
664: /**
665: * Convert the given JDBCException to an appropriate exception from the
666: * <code>org.springframework.dao</code> hierarchy.
667: * Uses a JDBC exception translator. Can be overridden in subclasses.
668: * @param ex JDBCException that occured, wrapping a SQLException
669: * @return the corresponding DataAccessException instance
670: * @see #setJdbcExceptionTranslator
671: */
672: protected DataAccessException convertJdbcAccessException(
673: JDBCException ex) {
674: return getJdbcExceptionTranslator().translate(
675: "Hibernate operation: " + ex.getMessage(), null,
676: ex.getSQLException());
677: }
678:
679: /**
680: * Hibernate transaction object, representing a SessionHolder.
681: * Used as transaction object by HibernateTransactionManager.
682: *
683: * <p>Derives from JdbcTransactionObjectSupport in order to inherit the
684: * capability to manage JDBC 3.0 Savepoints for underlying JDBC Connections.
685: *
686: * @see SessionHolder
687: */
688: private static class HibernateTransactionObject extends
689: JdbcTransactionObjectSupport {
690:
691: private SessionHolder sessionHolder;
692:
693: private boolean newSessionHolder;
694:
695: public void setSessionHolder(SessionHolder sessionHolder,
696: boolean newSessionHolder) {
697: this .sessionHolder = sessionHolder;
698: this .newSessionHolder = newSessionHolder;
699: }
700:
701: public SessionHolder getSessionHolder() {
702: return this .sessionHolder;
703: }
704:
705: public boolean isNewSessionHolder() {
706: return this .newSessionHolder;
707: }
708:
709: public boolean hasTransaction() {
710: return (this .sessionHolder != null && this .sessionHolder
711: .getTransaction() != null);
712: }
713:
714: public void setRollbackOnly() {
715: getSessionHolder().setRollbackOnly();
716: if (hasConnectionHolder()) {
717: getConnectionHolder().setRollbackOnly();
718: }
719: }
720:
721: public boolean isRollbackOnly() {
722: return getSessionHolder().isRollbackOnly()
723: || (hasConnectionHolder() && getConnectionHolder()
724: .isRollbackOnly());
725: }
726: }
727:
728: /**
729: * Holder for suspended resources.
730: * Used internally by <code>doSuspend</code> and <code>doResume</code>.
731: */
732: private static class SuspendedResourcesHolder {
733:
734: private final SessionHolder sessionHolder;
735:
736: private final ConnectionHolder connectionHolder;
737:
738: private SuspendedResourcesHolder(SessionHolder sessionHolder,
739: ConnectionHolder conHolder) {
740: this .sessionHolder = sessionHolder;
741: this .connectionHolder = conHolder;
742: }
743:
744: private SessionHolder getSessionHolder() {
745: return this .sessionHolder;
746: }
747:
748: private ConnectionHolder getConnectionHolder() {
749: return this.connectionHolder;
750: }
751: }
752:
753: }
|