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