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