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