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