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