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.util.HashMap;
020: import java.util.Iterator;
021: import java.util.Map;
022: import java.util.Set;
023:
024: import javax.sql.DataSource;
025: import javax.transaction.Status;
026: import javax.transaction.Transaction;
027: import javax.transaction.TransactionManager;
028:
029: import net.sf.hibernate.Criteria;
030: import net.sf.hibernate.FlushMode;
031: import net.sf.hibernate.HibernateException;
032: import net.sf.hibernate.Interceptor;
033: import net.sf.hibernate.JDBCException;
034: import net.sf.hibernate.NonUniqueResultException;
035: import net.sf.hibernate.ObjectDeletedException;
036: import net.sf.hibernate.PersistentObjectException;
037: import net.sf.hibernate.Query;
038: import net.sf.hibernate.QueryException;
039: import net.sf.hibernate.Session;
040: import net.sf.hibernate.SessionFactory;
041: import net.sf.hibernate.StaleObjectStateException;
042: import net.sf.hibernate.TransientObjectException;
043: import net.sf.hibernate.UnresolvableObjectException;
044: import net.sf.hibernate.WrongClassException;
045: import net.sf.hibernate.connection.ConnectionProvider;
046: import net.sf.hibernate.engine.SessionFactoryImplementor;
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049:
050: import org.springframework.core.CollectionFactory;
051: import org.springframework.dao.DataAccessException;
052: import org.springframework.dao.DataAccessResourceFailureException;
053: import org.springframework.dao.IncorrectResultSizeDataAccessException;
054: import org.springframework.dao.InvalidDataAccessApiUsageException;
055: import org.springframework.jdbc.datasource.DataSourceUtils;
056: import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
057: import org.springframework.jdbc.support.SQLExceptionTranslator;
058: import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
059: import org.springframework.transaction.jta.SpringJtaSynchronizationAdapter;
060: import org.springframework.transaction.support.TransactionSynchronizationManager;
061: import org.springframework.util.Assert;
062:
063: /**
064: * Helper class featuring methods for Hibernate Session handling,
065: * allowing for reuse of Hibernate Session instances within transactions.
066: * Also provides support for exception translation.
067: *
068: * <p>Supports synchronization with both Spring-managed JTA transactions
069: * (see {@link org.springframework.transaction.jta.JtaTransactionManager})
070: * and non-Spring JTA transactions (i.e. plain JTA or EJB CMT),
071: * transparently providing transaction-scoped Hibernate Sessions.
072: * Note that for non-Spring JTA transactions, a JTA TransactionManagerLookup
073: * has to be specified in the Hibernate configuration.
074: *
075: * <p>Used internally by {@link HibernateTemplate}, {@link HibernateInterceptor}
076: * and {@link HibernateTransactionManager}. Can also be used directly in
077: * application code.
078: *
079: * <p>Note: Spring's Hibernate support in this package requires Hibernate 2.1.
080: * Dedicated Hibernate3 support can be found in a separate package:
081: * <code>org.springframework.orm.hibernate3</code>.
082: *
083: * @author Juergen Hoeller
084: * @since 02.05.2003
085: * @see #getSession
086: * @see #releaseSession
087: * @see HibernateTransactionManager
088: * @see org.springframework.transaction.jta.JtaTransactionManager
089: * @see org.springframework.transaction.support.TransactionSynchronizationManager
090: */
091: public abstract class SessionFactoryUtils {
092:
093: /**
094: * Order value for TransactionSynchronization objects that clean up Hibernate Sessions.
095: * Returns <code>DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100</code>
096: * to execute Session cleanup before JDBC Connection cleanup, if any.
097: * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
098: */
099: public static final int SESSION_SYNCHRONIZATION_ORDER = DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
100:
101: static final Log logger = LogFactory
102: .getLog(SessionFactoryUtils.class);
103:
104: private static final ThreadLocal deferredCloseHolder = new ThreadLocal();
105:
106: /**
107: * Determine the DataSource of the given SessionFactory.
108: * @param sessionFactory the SessionFactory to check
109: * @return the DataSource, or <code>null</code> if none found
110: * @see net.sf.hibernate.engine.SessionFactoryImplementor#getConnectionProvider
111: * @see LocalDataSourceConnectionProvider
112: */
113: public static DataSource getDataSource(SessionFactory sessionFactory) {
114: if (sessionFactory instanceof SessionFactoryImplementor) {
115: ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory)
116: .getConnectionProvider();
117: if (cp instanceof LocalDataSourceConnectionProvider) {
118: return ((LocalDataSourceConnectionProvider) cp)
119: .getDataSource();
120: }
121: }
122: return null;
123: }
124:
125: /**
126: * Create an appropriate SQLExceptionTranslator for the given SessionFactory.
127: * If a DataSource is found, a SQLErrorCodeSQLExceptionTranslator for the DataSource
128: * is created; else, a SQLStateSQLExceptionTranslator as fallback.
129: * @param sessionFactory the SessionFactory to create the translator for
130: * @return the SQLExceptionTranslator
131: * @see #getDataSource
132: * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
133: * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
134: */
135: public static SQLExceptionTranslator newJdbcExceptionTranslator(
136: SessionFactory sessionFactory) {
137: DataSource ds = getDataSource(sessionFactory);
138: if (ds != null) {
139: return new SQLErrorCodeSQLExceptionTranslator(ds);
140: }
141: return new SQLStateSQLExceptionTranslator();
142: }
143:
144: /**
145: * Try to retrieve the JTA TransactionManager from the given SessionFactory
146: * and/or Session. Check the passed-in SessionFactory for implementing
147: * SessionFactoryImplementor (the usual case), falling back to the
148: * SessionFactory reference that the Session itself carries (for example,
149: * when using Hibernate's JCA Connector, i.e. JCASessionFactoryImpl).
150: * @param sessionFactory Hibernate SessionFactory
151: * @param session Hibernate Session (can also be <code>null</code>)
152: * @return the JTA TransactionManager, if any
153: * @see javax.transaction.TransactionManager
154: * @see SessionFactoryImplementor#getTransactionManager
155: * @see Session#getSessionFactory
156: * @see net.sf.hibernate.impl.SessionFactoryImpl
157: * @see net.sf.hibernate.jca.JCASessionFactoryImpl
158: */
159: public static TransactionManager getJtaTransactionManager(
160: SessionFactory sessionFactory, Session session) {
161: SessionFactoryImplementor sessionFactoryImpl = null;
162: if (sessionFactory instanceof SessionFactoryImplementor) {
163: sessionFactoryImpl = ((SessionFactoryImplementor) sessionFactory);
164: } else if (session != null) {
165: SessionFactory internalFactory = session
166: .getSessionFactory();
167: if (internalFactory instanceof SessionFactoryImplementor) {
168: sessionFactoryImpl = (SessionFactoryImplementor) internalFactory;
169: }
170: }
171: return (sessionFactoryImpl != null ? sessionFactoryImpl
172: .getTransactionManager() : null);
173: }
174:
175: /**
176: * Get a Hibernate Session for the given SessionFactory. Is aware of and will
177: * return any existing corresponding Session bound to the current thread, for
178: * example when using {@link HibernateTransactionManager}. Will create a new
179: * Session otherwise, if "allowCreate" is <code>true</code>.
180: * <p>This is the <code>getSession</code> method used by typical data access code,
181: * in combination with <code>releaseSession</code> called when done with
182: * the Session. Note that HibernateTemplate allows to write data access code
183: * without caring about such resource handling.
184: * @param sessionFactory Hibernate SessionFactory to create the session with
185: * @param allowCreate whether a non-transactional Session should be created
186: * when no transactional Session can be found for the current thread
187: * @return the Hibernate Session
188: * @throws DataAccessResourceFailureException if the Session couldn't be created
189: * @throws IllegalStateException if no thread-bound Session found and
190: * "allowCreate" is <code>false</code>
191: * @see #releaseSession
192: * @see HibernateTemplate
193: */
194: public static Session getSession(SessionFactory sessionFactory,
195: boolean allowCreate)
196: throws DataAccessResourceFailureException,
197: IllegalStateException {
198:
199: return getSession(sessionFactory, null, null, allowCreate);
200: }
201:
202: /**
203: * Get a Hibernate Session for the given SessionFactory. Is aware of and will
204: * return any existing corresponding Session bound to the current thread, for
205: * example when using {@link HibernateTransactionManager}. Will always create
206: * a new Session otherwise.
207: * <p>Supports setting a Session-level Hibernate entity interceptor that allows
208: * to inspect and change property values before writing to and reading from the
209: * database. Such an interceptor can also be set at the SessionFactory level
210: * (i.e. on LocalSessionFactoryBean), on HibernateTransactionManager, or on
211: * HibernateInterceptor/HibernateTemplate.
212: * @param sessionFactory Hibernate SessionFactory to create the session with
213: * @param entityInterceptor Hibernate entity interceptor, or <code>null</code> if none
214: * @param jdbcExceptionTranslator SQLExceptionTranslator to use for flushing the
215: * Session on transaction synchronization (may be <code>null</code>; only used
216: * when actually registering a transaction synchronization)
217: * @return the Hibernate Session
218: * @throws DataAccessResourceFailureException if the Session couldn't be created
219: * @see LocalSessionFactoryBean#setEntityInterceptor
220: * @see HibernateInterceptor#setEntityInterceptor
221: * @see HibernateTemplate#setEntityInterceptor
222: */
223: public static Session getSession(SessionFactory sessionFactory,
224: Interceptor entityInterceptor,
225: SQLExceptionTranslator jdbcExceptionTranslator)
226: throws DataAccessResourceFailureException {
227:
228: return getSession(sessionFactory, entityInterceptor,
229: jdbcExceptionTranslator, true);
230: }
231:
232: /**
233: * Get a Hibernate Session for the given SessionFactory. Is aware of and will
234: * return any existing corresponding Session bound to the current thread, for
235: * example when using {@link HibernateTransactionManager}. Will create a new
236: * Session otherwise, if "allowCreate" is <code>true</code>.
237: * @param sessionFactory Hibernate SessionFactory to create the session with
238: * @param entityInterceptor Hibernate entity interceptor, or <code>null</code> if none
239: * @param jdbcExceptionTranslator SQLExceptionTranslator to use for flushing the
240: * Session on transaction synchronization (may be <code>null</code>)
241: * @param allowCreate whether a non-transactional Session should be created
242: * when no transactional Session can be found for the current thread
243: * @return the Hibernate Session
244: * @throws DataAccessResourceFailureException if the Session couldn't be created
245: * @throws IllegalStateException if no thread-bound Session found and
246: * "allowCreate" is <code>false</code>
247: */
248: private static Session getSession(SessionFactory sessionFactory,
249: Interceptor entityInterceptor,
250: SQLExceptionTranslator jdbcExceptionTranslator,
251: boolean allowCreate)
252: throws DataAccessResourceFailureException,
253: IllegalStateException {
254:
255: Assert.notNull(sessionFactory, "No SessionFactory specified");
256:
257: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
258: .getResource(sessionFactory);
259: if (sessionHolder != null && !sessionHolder.isEmpty()) {
260: // pre-bound Hibernate Session
261: Session session = null;
262: if (TransactionSynchronizationManager
263: .isSynchronizationActive()
264: && sessionHolder.doesNotHoldNonDefaultSession()) {
265: // Spring transaction management is active ->
266: // register pre-bound Session with it for transactional flushing.
267: session = sessionHolder.getValidatedSession();
268: if (!sessionHolder.isSynchronizedWithTransaction()) {
269: logger
270: .debug("Registering Spring transaction synchronization for existing Hibernate Session");
271: TransactionSynchronizationManager
272: .registerSynchronization(new SpringSessionSynchronization(
273: sessionHolder, sessionFactory,
274: jdbcExceptionTranslator, false));
275: sessionHolder.setSynchronizedWithTransaction(true);
276: // Switch to FlushMode.AUTO if we're not within a read-only transaction.
277: FlushMode flushMode = session.getFlushMode();
278: if (FlushMode.NEVER.equals(flushMode)
279: && !TransactionSynchronizationManager
280: .isCurrentTransactionReadOnly()) {
281: session.setFlushMode(FlushMode.AUTO);
282: sessionHolder.setPreviousFlushMode(flushMode);
283: }
284: }
285: } else {
286: // No Spring transaction management active -> try JTA transaction synchronization.
287: session = getJtaSynchronizedSession(sessionHolder,
288: sessionFactory, jdbcExceptionTranslator);
289: }
290: if (session != null) {
291: return session;
292: }
293: }
294:
295: try {
296: logger.debug("Opening Hibernate Session");
297: Session session = (entityInterceptor != null ? sessionFactory
298: .openSession(entityInterceptor)
299: : sessionFactory.openSession());
300:
301: // Set Session to FlushMode.NEVER if we're within a read-only transaction.
302: // Use same Session for further Hibernate actions within the transaction.
303: // Thread object will get removed by synchronization at transaction completion.
304: if (TransactionSynchronizationManager
305: .isSynchronizationActive()) {
306: // We're within a Spring-managed transaction, possibly from JtaTransactionManager.
307: logger
308: .debug("Registering Spring transaction synchronization for new Hibernate Session");
309: SessionHolder holderToUse = sessionHolder;
310: if (holderToUse == null) {
311: holderToUse = new SessionHolder(session);
312: } else {
313: holderToUse.addSession(session);
314: }
315: if (TransactionSynchronizationManager
316: .isCurrentTransactionReadOnly()) {
317: session.setFlushMode(FlushMode.NEVER);
318: }
319: TransactionSynchronizationManager
320: .registerSynchronization(new SpringSessionSynchronization(
321: holderToUse, sessionFactory,
322: jdbcExceptionTranslator, true));
323: holderToUse.setSynchronizedWithTransaction(true);
324: if (holderToUse != sessionHolder) {
325: TransactionSynchronizationManager.bindResource(
326: sessionFactory, holderToUse);
327: }
328: } else {
329: // No Spring transaction management active -> try JTA transaction synchronization.
330: registerJtaSynchronization(session, sessionFactory,
331: jdbcExceptionTranslator, sessionHolder);
332: }
333:
334: // Check whether we are allowed to return the Session.
335: if (!allowCreate
336: && !isSessionTransactional(session, sessionFactory)) {
337: closeSession(session);
338: throw new IllegalStateException(
339: "No Hibernate Session bound to thread, "
340: + "and configuration does not allow creation of non-transactional one here");
341: }
342:
343: return session;
344: } catch (HibernateException ex) {
345: throw new DataAccessResourceFailureException(
346: "Could not open Hibernate Session", ex);
347: }
348: }
349:
350: /**
351: * Retrieve a Session from the given SessionHolder, potentially from a
352: * JTA transaction synchronization.
353: * @param sessionHolder the SessionHolder to check
354: * @param sessionFactory the SessionFactory to get the JTA TransactionManager from
355: * @param jdbcExceptionTranslator SQLExceptionTranslator to use for flushing the
356: * Session on transaction synchronization (may be <code>null</code>)
357: * @return the associated Session, if any
358: * @throws DataAccessResourceFailureException if the Session couldn't be created
359: */
360: private static Session getJtaSynchronizedSession(
361: SessionHolder sessionHolder, SessionFactory sessionFactory,
362: SQLExceptionTranslator jdbcExceptionTranslator)
363: throws DataAccessResourceFailureException {
364:
365: // JTA synchronization is only possible with a javax.transaction.TransactionManager.
366: // We'll check the Hibernate SessionFactory: If a TransactionManagerLookup is specified
367: // in Hibernate configuration, it will contain a TransactionManager reference.
368: TransactionManager jtaTm = getJtaTransactionManager(
369: sessionFactory, sessionHolder.getAnySession());
370: if (jtaTm != null) {
371: // Check whether JTA transaction management is active ->
372: // fetch pre-bound Session for the current JTA transaction, if any.
373: // (just necessary for JTA transaction suspension, with an individual
374: // Hibernate Session per currently active/suspended transaction)
375: try {
376: // Look for transaction-specific Session.
377: Transaction jtaTx = jtaTm.getTransaction();
378: if (jtaTx != null) {
379: int jtaStatus = jtaTx.getStatus();
380: if (jtaStatus == Status.STATUS_ACTIVE
381: || jtaStatus == Status.STATUS_MARKED_ROLLBACK) {
382: Session session = sessionHolder
383: .getValidatedSession(jtaTx);
384: if (session == null
385: && !sessionHolder
386: .isSynchronizedWithTransaction()) {
387: // No transaction-specific Session found: If not already marked as
388: // synchronized with transaction, register the default thread-bound
389: // Session as JTA-transactional. If there is no default Session,
390: // we're a new inner JTA transaction with an outer one being suspended:
391: // In that case, we'll return null to trigger opening of a new Session.
392: session = sessionHolder
393: .getValidatedSession();
394: if (session != null) {
395: logger
396: .debug("Registering JTA transaction synchronization for existing Hibernate Session");
397: sessionHolder
398: .addSession(jtaTx, session);
399: jtaTx
400: .registerSynchronization(new SpringJtaSynchronizationAdapter(
401: new SpringSessionSynchronization(
402: sessionHolder,
403: sessionFactory,
404: jdbcExceptionTranslator,
405: false), jtaTm));
406: sessionHolder
407: .setSynchronizedWithTransaction(true);
408: // Switch to FlushMode.AUTO if we're not within a read-only transaction.
409: FlushMode flushMode = session
410: .getFlushMode();
411: if (FlushMode.NEVER.equals(flushMode)) {
412: session
413: .setFlushMode(FlushMode.AUTO);
414: sessionHolder
415: .setPreviousFlushMode(flushMode);
416: }
417: }
418: }
419: return session;
420: }
421: }
422: // No transaction active -> simply return default thread-bound Session, if any
423: // (possibly from OpenSessionInViewFilter/Interceptor).
424: return sessionHolder.getValidatedSession();
425: } catch (Throwable ex) {
426: throw new DataAccessResourceFailureException(
427: "Could not check JTA transaction", ex);
428: }
429: } else {
430: // No JTA TransactionManager -> simply return default thread-bound Session, if any
431: // (possibly from OpenSessionInViewFilter/Interceptor).
432: return sessionHolder.getValidatedSession();
433: }
434: }
435:
436: /**
437: * Register a JTA synchronization for the given Session, if any.
438: * @param sessionHolder the existing thread-bound SessionHolder, if any
439: * @param session the Session to register
440: * @param sessionFactory the SessionFactory that the Session was created with
441: * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
442: * Session on transaction synchronization (may be <code>null</code>)
443: */
444: private static void registerJtaSynchronization(Session session,
445: SessionFactory sessionFactory,
446: SQLExceptionTranslator jdbcExceptionTranslator,
447: SessionHolder sessionHolder) {
448:
449: // JTA synchronization is only possible with a javax.transaction.TransactionManager.
450: // We'll check the Hibernate SessionFactory: If a TransactionManagerLookup is specified
451: // in Hibernate configuration, it will contain a TransactionManager reference.
452: TransactionManager jtaTm = getJtaTransactionManager(
453: sessionFactory, session);
454: if (jtaTm != null) {
455: try {
456: Transaction jtaTx = jtaTm.getTransaction();
457: if (jtaTx != null) {
458: int jtaStatus = jtaTx.getStatus();
459: if (jtaStatus == Status.STATUS_ACTIVE
460: || jtaStatus == Status.STATUS_MARKED_ROLLBACK) {
461: logger
462: .debug("Registering JTA transaction synchronization for new Hibernate Session");
463: SessionHolder holderToUse = sessionHolder;
464: // Register JTA Transaction with existing SessionHolder.
465: // Create a new SessionHolder if none existed before.
466: if (holderToUse == null) {
467: holderToUse = new SessionHolder(jtaTx,
468: session);
469: } else {
470: holderToUse.addSession(jtaTx, session);
471: }
472: jtaTx
473: .registerSynchronization(new SpringJtaSynchronizationAdapter(
474: new SpringSessionSynchronization(
475: holderToUse,
476: sessionFactory,
477: jdbcExceptionTranslator,
478: true), jtaTm));
479: holderToUse
480: .setSynchronizedWithTransaction(true);
481: if (holderToUse != sessionHolder) {
482: TransactionSynchronizationManager
483: .bindResource(sessionFactory,
484: holderToUse);
485: }
486: }
487: }
488: } catch (Throwable ex) {
489: throw new DataAccessResourceFailureException(
490: "Could not register synchronization with JTA TransactionManager",
491: ex);
492: }
493: }
494: }
495:
496: /**
497: * Get a new Hibernate Session from the given SessionFactory.
498: * Will return a new Session even if there already is a pre-bound
499: * Session for the given SessionFactory.
500: * <p>Within a transaction, this method will create a new Session
501: * that shares the transaction's JDBC Connection. More specifically,
502: * it will use the same JDBC Connection as the pre-bound Hibernate Session.
503: * @param sessionFactory Hibernate SessionFactory to create the session with
504: * @return the new Session
505: */
506: public static Session getNewSession(SessionFactory sessionFactory) {
507: return getNewSession(sessionFactory, null);
508: }
509:
510: /**
511: * Get a new Hibernate Session from the given SessionFactory.
512: * Will return a new Session even if there already is a pre-bound
513: * Session for the given SessionFactory.
514: * <p>Within a transaction, this method will create a new Session
515: * that shares the transaction's JDBC Connection. More specifically,
516: * it will use the same JDBC Connection as the pre-bound Hibernate Session.
517: * @param sessionFactory Hibernate SessionFactory to create the session with
518: * @param entityInterceptor Hibernate entity interceptor, or <code>null</code> if none
519: * @return the new Session
520: */
521: public static Session getNewSession(SessionFactory sessionFactory,
522: Interceptor entityInterceptor) {
523: Assert.notNull(sessionFactory, "No SessionFactory specified");
524:
525: try {
526: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
527: .getResource(sessionFactory);
528: if (sessionHolder != null && !sessionHolder.isEmpty()) {
529: if (entityInterceptor != null) {
530: return sessionFactory.openSession(sessionHolder
531: .getAnySession().connection(),
532: entityInterceptor);
533: } else {
534: return sessionFactory.openSession(sessionHolder
535: .getAnySession().connection());
536: }
537: } else {
538: if (entityInterceptor != null) {
539: return sessionFactory
540: .openSession(entityInterceptor);
541: } else {
542: return sessionFactory.openSession();
543: }
544: }
545: } catch (HibernateException ex) {
546: throw new DataAccessResourceFailureException(
547: "Could not open Hibernate Session", ex);
548: }
549: }
550:
551: /**
552: * Return whether the given Hibernate Session is transactional, that is,
553: * bound to the current thread by Spring's transaction facilities.
554: * @param session the Hibernate Session to check
555: * @param sessionFactory Hibernate SessionFactory that the Session was created with
556: * (may be <code>null</code>)
557: * @return whether the Session is transactional
558: */
559: public static boolean isSessionTransactional(Session session,
560: SessionFactory sessionFactory) {
561: if (sessionFactory == null) {
562: return false;
563: }
564: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
565: .getResource(sessionFactory);
566: return (sessionHolder != null && sessionHolder
567: .containsSession(session));
568: }
569:
570: /**
571: * Apply the current transaction timeout, if any, to the given
572: * Hibernate Query object.
573: * @param query the Hibernate Query object
574: * @param sessionFactory Hibernate SessionFactory that the Query was created for
575: * (may be <code>null</code>)
576: * @see net.sf.hibernate.Query#setTimeout
577: */
578: public static void applyTransactionTimeout(Query query,
579: SessionFactory sessionFactory) {
580: Assert.notNull(query, "No Query object specified");
581: if (sessionFactory != null) {
582: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
583: .getResource(sessionFactory);
584: if (sessionHolder != null && sessionHolder.hasTimeout()) {
585: query
586: .setTimeout(sessionHolder
587: .getTimeToLiveInSeconds());
588: }
589: }
590: }
591:
592: /**
593: * Apply the current transaction timeout, if any, to the given
594: * Hibernate Criteria object.
595: * @param criteria the Hibernate Criteria object
596: * @param sessionFactory Hibernate SessionFactory that the Criteria was created for
597: * @see net.sf.hibernate.Criteria#setTimeout
598: */
599: public static void applyTransactionTimeout(Criteria criteria,
600: SessionFactory sessionFactory) {
601: Assert.notNull(criteria, "No Criteria object specified");
602: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
603: .getResource(sessionFactory);
604: if (sessionHolder != null && sessionHolder.hasTimeout()) {
605: criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds());
606: }
607: }
608:
609: /**
610: * Convert the given HibernateException to an appropriate exception
611: * from the <code>org.springframework.dao</code> hierarchy.
612: * <p>Note that it is advisable to handle {@link net.sf.hibernate.JDBCException}
613: * specifically by using a {@link org.springframework.jdbc.support.SQLExceptionTranslator}
614: * for the underlying SQLException.
615: * @param ex HibernateException that occured
616: * @return the corresponding DataAccessException instance
617: * @see HibernateAccessor#convertHibernateAccessException
618: * @see HibernateTransactionManager#convertHibernateAccessException
619: */
620: public static DataAccessException convertHibernateAccessException(
621: HibernateException ex) {
622: if (ex instanceof JDBCException) {
623: // SQLException during Hibernate access: only passed in here from custom code,
624: // as HibernateTemplate etc will use SQLExceptionTranslator-based handling.
625: return new HibernateJdbcException((JDBCException) ex);
626: }
627:
628: if (ex instanceof PersistentObjectException) {
629: return new InvalidDataAccessApiUsageException(ex
630: .getMessage(), ex);
631: }
632: if (ex instanceof TransientObjectException) {
633: return new InvalidDataAccessApiUsageException(ex
634: .getMessage(), ex);
635: }
636: if (ex instanceof ObjectDeletedException) {
637: return new InvalidDataAccessApiUsageException(ex
638: .getMessage(), ex);
639: }
640: if (ex instanceof QueryException) {
641: return new HibernateQueryException((QueryException) ex);
642: }
643: if (ex instanceof UnresolvableObjectException) {
644: return new HibernateObjectRetrievalFailureException(
645: (UnresolvableObjectException) ex);
646: }
647: if (ex instanceof WrongClassException) {
648: return new HibernateObjectRetrievalFailureException(
649: (WrongClassException) ex);
650: }
651: if (ex instanceof NonUniqueResultException) {
652: return new IncorrectResultSizeDataAccessException(ex
653: .getMessage(), 1);
654: }
655: if (ex instanceof StaleObjectStateException) {
656: return new HibernateOptimisticLockingFailureException(
657: (StaleObjectStateException) ex);
658: }
659:
660: // fallback
661: return new HibernateSystemException(ex);
662: }
663:
664: /**
665: * Determine whether deferred close is active for the current thread
666: * and the given SessionFactory.
667: * @param sessionFactory the Hibernate SessionFactory to check
668: * @return whether deferred close is active
669: */
670: public static boolean isDeferredCloseActive(
671: SessionFactory sessionFactory) {
672: Assert.notNull(sessionFactory, "No SessionFactory specified");
673: Map holderMap = (Map) deferredCloseHolder.get();
674: return (holderMap != null && holderMap
675: .containsKey(sessionFactory));
676: }
677:
678: /**
679: * Initialize deferred close for the current thread and the given SessionFactory.
680: * Sessions will not be actually closed on close calls then, but rather at a
681: * processDeferredClose call at a finishing point (like request completion).
682: * <p>Used by OpenSessionInViewFilter and OpenSessionInViewInterceptor
683: * when not configured for a single session.
684: * @param sessionFactory Hibernate SessionFactory
685: * @see #processDeferredClose
686: * @see #releaseSession
687: * @see org.springframework.orm.hibernate.support.OpenSessionInViewFilter#setSingleSession
688: * @see org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor#setSingleSession
689: */
690: public static void initDeferredClose(SessionFactory sessionFactory) {
691: Assert.notNull(sessionFactory, "No SessionFactory specified");
692: logger
693: .debug("Initializing deferred close of Hibernate Sessions");
694: Map holderMap = (Map) deferredCloseHolder.get();
695: if (holderMap == null) {
696: holderMap = new HashMap();
697: deferredCloseHolder.set(holderMap);
698: }
699: holderMap.put(sessionFactory, CollectionFactory
700: .createLinkedSetIfPossible(4));
701: }
702:
703: /**
704: * Process Sessions that have been registered for deferred close
705: * for the given SessionFactory.
706: * @param sessionFactory Hibernate SessionFactory
707: * @see #initDeferredClose
708: * @see #releaseSession
709: */
710: public static void processDeferredClose(
711: SessionFactory sessionFactory) {
712: Assert.notNull(sessionFactory, "No SessionFactory specified");
713:
714: Map holderMap = (Map) deferredCloseHolder.get();
715: if (holderMap == null || !holderMap.containsKey(sessionFactory)) {
716: throw new IllegalStateException(
717: "Deferred close not active for SessionFactory ["
718: + sessionFactory + "]");
719: }
720:
721: logger.debug("Processing deferred close of Hibernate Sessions");
722: Set sessions = (Set) holderMap.remove(sessionFactory);
723: for (Iterator it = sessions.iterator(); it.hasNext();) {
724: closeSession((Session) it.next());
725: }
726:
727: if (holderMap.isEmpty()) {
728: deferredCloseHolder.set(null);
729: }
730: }
731:
732: /**
733: * Close the given Session, created via the given factory,
734: * if it is not managed externally (i.e. not bound to the thread).
735: * @param session the Hibernate Session to close (may be <code>null</code>)
736: * @param sessionFactory Hibernate SessionFactory that the Session was created with
737: * (may be <code>null</code>)
738: */
739: public static void releaseSession(Session session,
740: SessionFactory sessionFactory) {
741: if (session == null) {
742: return;
743: }
744: // Only close non-transactional Sessions.
745: if (!isSessionTransactional(session, sessionFactory)) {
746: closeSessionOrRegisterDeferredClose(session, sessionFactory);
747: }
748: }
749:
750: /**
751: * Close the given Session or register it for deferred close.
752: * @param session the Hibernate Session to close
753: * @param sessionFactory Hibernate SessionFactory that the Session was created with
754: * (may be <code>null</code>)
755: * @see #initDeferredClose
756: * @see #processDeferredClose
757: */
758: static void closeSessionOrRegisterDeferredClose(Session session,
759: SessionFactory sessionFactory) {
760: Map holderMap = (Map) deferredCloseHolder.get();
761: if (holderMap != null && sessionFactory != null
762: && holderMap.containsKey(sessionFactory)) {
763: logger
764: .debug("Registering Hibernate Session for deferred close");
765: // Switch Session to FlushMode.NEVER for remaining lifetime.
766: session.setFlushMode(FlushMode.NEVER);
767: Set sessions = (Set) holderMap.get(sessionFactory);
768: sessions.add(session);
769: } else {
770: closeSession(session);
771: }
772: }
773:
774: /**
775: * Perform actual closing of the Hibernate Session,
776: * catching and logging any cleanup exceptions thrown.
777: * @param session the Hibernate Session to close (may be <code>null</code>)
778: * @see net.sf.hibernate.Session#close()
779: */
780: public static void closeSession(Session session) {
781: if (session != null) {
782: logger.debug("Closing Hibernate Session");
783: try {
784: session.close();
785: } catch (HibernateException ex) {
786: logger.debug("Could not close Hibernate Session", ex);
787: } catch (Throwable ex) {
788: logger
789: .debug(
790: "Unexpected exception on closing Hibernate Session",
791: ex);
792: }
793: }
794: }
795:
796: }
|