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