1. /*
  2. * Copyright 2002-2004 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. package org.springframework.orm.hibernate;
  17. import javax.transaction.Status;
  18. import javax.transaction.Synchronization;
  19. import javax.transaction.SystemException;
  20. import javax.transaction.TransactionManager;
  21. import net.sf.hibernate.Criteria;
  22. import net.sf.hibernate.FlushMode;
  23. import net.sf.hibernate.HibernateException;
  24. import net.sf.hibernate.Interceptor;
  25. import net.sf.hibernate.JDBCException;
  26. import net.sf.hibernate.ObjectDeletedException;
  27. import net.sf.hibernate.ObjectNotFoundException;
  28. import net.sf.hibernate.PersistentObjectException;
  29. import net.sf.hibernate.Query;
  30. import net.sf.hibernate.QueryException;
  31. import net.sf.hibernate.Session;
  32. import net.sf.hibernate.SessionFactory;
  33. import net.sf.hibernate.StaleObjectStateException;
  34. import net.sf.hibernate.TransientObjectException;
  35. import net.sf.hibernate.UnresolvableObjectException;
  36. import net.sf.hibernate.WrongClassException;
  37. import net.sf.hibernate.engine.SessionFactoryImplementor;
  38. import net.sf.hibernate.engine.SessionImplementor;
  39. import org.apache.commons.logging.Log;
  40. import org.apache.commons.logging.LogFactory;
  41. import org.springframework.dao.CleanupFailureDataAccessException;
  42. import org.springframework.dao.DataAccessException;
  43. import org.springframework.dao.DataAccessResourceFailureException;
  44. import org.springframework.dao.InvalidDataAccessApiUsageException;
  45. import org.springframework.jdbc.support.SQLExceptionTranslator;
  46. import org.springframework.transaction.support.TransactionSynchronization;
  47. import org.springframework.transaction.support.TransactionSynchronizationManager;
  48. /**
  49. * Helper class featuring methods for Hibernate session handling,
  50. * allowing for reuse of Hibernate Session instances within transactions.
  51. *
  52. * <p>Supports synchronization with both Spring-managed JTA transactions
  53. * (i.e. JtaTransactionManager) and non-Spring JTA transactions (i.e. plain JTA
  54. * or EJB CMT). See the getSession version with all parameters for details.
  55. *
  56. * <p>Used internally by HibernateTemplate, HibernateInterceptor, and
  57. * HibernateTransactionManager. Can also be used directly in application code,
  58. * e.g. in combination with HibernateInterceptor.
  59. *
  60. * <p>Note: Spring's Hibernate support requires Hibernate 2.1 (as of Spring 1.0).
  61. *
  62. * @author Juergen Hoeller
  63. * @since 02.05.2003
  64. * @see #getSession(SessionFactory, Interceptor, SQLExceptionTranslator, boolean)
  65. * @see HibernateTemplate
  66. * @see HibernateInterceptor
  67. * @see HibernateTransactionManager
  68. * @see org.springframework.transaction.jta.JtaTransactionManager
  69. */
  70. public abstract class SessionFactoryUtils {
  71. private static final Log logger = LogFactory.getLog(SessionFactoryUtils.class);
  72. /**
  73. * Get a Hibernate Session for the given SessionFactory. Is aware of and will
  74. * return any existing corresponding Session bound to the current thread, for
  75. * example when using HibernateTransactionManager. Will create a new Session
  76. * otherwise, if allowCreate is true.
  77. * <p>This is the getSession method used by typical data access code, in
  78. * combination with closeSessionIfNecessary called when done with the Session.
  79. * Note that HibernateTemplate allows to write data access code without caring
  80. * about such resource handling.
  81. * <p>Supports synchronization with both Spring-managed JTA transactions
  82. * (i.e. JtaTransactionManager) and non-Spring JTA transactions (i.e. plain JTA
  83. * or EJB CMT). See the getSession version with all parameters for details.
  84. * @param sessionFactory Hibernate SessionFactory to create the session with
  85. * @param allowCreate if a new Session should be created if no thread-bound found
  86. * @return the Hibernate Session
  87. * @throws DataAccessResourceFailureException if the Session couldn't be created
  88. * @throws IllegalStateException if no thread-bound Session found and allowCreate false
  89. * @see #getSession(SessionFactory, Interceptor, SQLExceptionTranslator, boolean)
  90. * @see #closeSessionIfNecessary
  91. * @see HibernateTemplate
  92. */
  93. public static Session getSession(SessionFactory sessionFactory, boolean allowCreate)
  94. throws DataAccessResourceFailureException, IllegalStateException {
  95. if (!TransactionSynchronizationManager.hasResource(sessionFactory) && !allowCreate) {
  96. throw new IllegalStateException("No Hibernate Session bound to thread, and configuration " +
  97. "does not allow creation of new one here");
  98. }
  99. return getSession(sessionFactory, null, null, true);
  100. }
  101. /**
  102. * Get a Hibernate Session for the given SessionFactory. Is aware of and will
  103. * return any existing corresponding Session bound to the current thread, for
  104. * example when using HibernateTransactionManager. Will always create a new
  105. * Session otherwise.
  106. * <p>Supports synchronization with both Spring-managed JTA transactions
  107. * (i.e. JtaTransactionManager) and non-Spring JTA transactions (i.e. plain JTA
  108. * or EJB CMT). See the getSession version with all parameters for details.
  109. * @param sessionFactory Hibernate SessionFactory to create the session with
  110. * @param entityInterceptor Hibernate entity interceptor, or null if none
  111. * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
  112. * Session on transaction synchronization (can be null; only used when actually
  113. * registering a transaction synchronization)
  114. * @return the Hibernate Session
  115. * @throws DataAccessResourceFailureException if the Session couldn't be created
  116. * @see #getSession(SessionFactory, Interceptor, SQLExceptionTranslator, boolean)
  117. */
  118. public static Session getSession(SessionFactory sessionFactory, Interceptor entityInterceptor,
  119. SQLExceptionTranslator jdbcExceptionTranslator) {
  120. return getSession(sessionFactory, entityInterceptor, jdbcExceptionTranslator, true);
  121. }
  122. /**
  123. * Get a Hibernate Session for the given SessionFactory. Is aware of and will
  124. * return any existing corresponding Session bound to the current thread, for
  125. * example when using HibernateTransactionManager. Will always create a new
  126. * Session otherwise.
  127. * <p>Supports synchronization with Spring-managed JTA transactions
  128. * (i.e. JtaTransactionManager) via TransactionSynchronizationManager, to allow
  129. * for transaction-scoped Hibernate Sessions and proper transactional handling
  130. * of the JVM-level cache. This will only occur if "allowSynchronization" is true.
  131. * <p>Supports synchronization with non-Spring JTA transactions (i.e. plain JTA
  132. * or EJB CMT) via TransactionSynchronizationManager, to allow for
  133. * transaction-scoped Hibernate Sessions without JtaTransactionManager.
  134. * This only applies when a JTA TransactionManagerLookup is specified in the
  135. * Hibernate configuration, and when "allowSynchronization" is true.
  136. * <p>Supports setting a Session-level Hibernate entity interceptor that allows
  137. * to inspect and change property values before writing to and reading from the
  138. * database. Such an interceptor can also be set at the SessionFactory level
  139. * (i.e. on LocalSessionFactoryBean), on HibernateTransactionManager, or on
  140. * HibernateInterceptor/HibernateTemplate.
  141. * @param sessionFactory Hibernate SessionFactory to create the session with
  142. * @param entityInterceptor Hibernate entity interceptor, or null if none
  143. * @param jdbcExceptionTranslator SQLExcepionTranslator to use for flushing the
  144. * Session on transaction synchronization (can be null; only used when actually
  145. * registering a transaction synchronization)
  146. * @param allowSynchronization if a new Hibernate Session is supposed to be
  147. * registered with transaction synchronization (if synchronization is active).
  148. * This will always be true for typical data access code.
  149. * @return the Hibernate Session
  150. * @throws DataAccessResourceFailureException if the Session couldn't be created
  151. * @see LocalSessionFactoryBean#setEntityInterceptor
  152. * @see HibernateInterceptor#setEntityInterceptor
  153. * @see HibernateTemplate#setEntityInterceptor
  154. * @see HibernateTransactionManager
  155. * @see org.springframework.transaction.jta.JtaTransactionManager
  156. * @see org.springframework.transaction.support.TransactionSynchronizationManager
  157. */
  158. public static Session getSession(SessionFactory sessionFactory, Interceptor entityInterceptor,
  159. SQLExceptionTranslator jdbcExceptionTranslator, boolean allowSynchronization)
  160. throws DataAccessResourceFailureException {
  161. SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  162. if (sessionHolder != null) {
  163. if (allowSynchronization && TransactionSynchronizationManager.isSynchronizationActive() &&
  164. !sessionHolder.isSynchronizedWithTransaction()) {
  165. TransactionSynchronizationManager.registerSynchronization(
  166. new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false));
  167. sessionHolder.setSynchronizedWithTransaction(true);
  168. }
  169. return sessionHolder.getSession();
  170. }
  171. try {
  172. logger.debug("Opening Hibernate session");
  173. Session session = (entityInterceptor != null ?
  174. sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession());
  175. if (allowSynchronization) {
  176. // Use same Session for further Hibernate actions within the transaction.
  177. // Thread object will get removed by synchronization at transaction completion.
  178. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  179. // We're within a Spring-managed transaction, possibly from JtaTransactionManager.
  180. logger.debug("Registering Spring transaction synchronization for Hibernate session");
  181. sessionHolder = new SessionHolder(session);
  182. sessionHolder.setSynchronizedWithTransaction(true);
  183. TransactionSynchronizationManager.registerSynchronization(
  184. new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, true));
  185. TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
  186. }
  187. else {
  188. // JTA synchronization is only possible with a javax.transaction.TransactionManager.
  189. // We'll check the Hibernate SessionFactory: If a TransactionManagerLookup is specified
  190. // in Hibernate configuration, it will contain a TransactionManager reference.
  191. TransactionManager jtaTm = getJtaTransactionManager(sessionFactory, session);
  192. if (jtaTm != null) {
  193. try {
  194. if (jtaTm.getStatus() == Status.STATUS_ACTIVE || jtaTm.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
  195. logger.debug("Registering JTA transaction synchronization for Hibernate session");
  196. sessionHolder = new SessionHolder(session);
  197. sessionHolder.setSynchronizedWithTransaction(true);
  198. jtaTm.getTransaction().registerSynchronization(
  199. new JtaSessionSynchronization(
  200. new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, true),
  201. jtaTm));
  202. TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
  203. }
  204. }
  205. catch (Exception ex) {
  206. throw new DataAccessResourceFailureException("Could not register synchronization " +
  207. "with JTA TransactionManager", ex);
  208. }
  209. }
  210. }
  211. }
  212. return session;
  213. }
  214. catch (JDBCException ex) {
  215. // SQLException underneath
  216. throw new DataAccessResourceFailureException("Could not open Hibernate session", ex.getSQLException());
  217. }
  218. catch (HibernateException ex) {
  219. throw new DataAccessResourceFailureException("Could not open Hibernate session", ex);
  220. }
  221. }
  222. /**
  223. * Try to retrieve the JTA TransactionManager from the given SessionFactory
  224. * and/or Session. Check the passed-in SessionFactory for implementing
  225. * SessionFactoryImplementor (the usual case), falling back to the
  226. * SessionFactory reference that the Session itself carries (for example,
  227. * when using Hibernate's JCA Connector, i.e. JCASessionFactoryImpl).
  228. * @param sessionFactory passed-in Hibernate SessionFactory
  229. * @param session newly created Hibernate Session
  230. * @return the JTA TransactionManager, if any
  231. * @see javax.transaction.TransactionManager
  232. * @see SessionFactoryImplementor#getTransactionManager
  233. * @see Session#getSessionFactory
  234. * @see net.sf.hibernate.impl.SessionFactoryImpl
  235. * @see net.sf.hibernate.jca.JCASessionFactoryImpl
  236. */
  237. private static TransactionManager getJtaTransactionManager(SessionFactory sessionFactory, Session session) {
  238. SessionFactoryImplementor sessionFactoryImpl = null;
  239. if (sessionFactory instanceof SessionFactoryImplementor) {
  240. sessionFactoryImpl = ((SessionFactoryImplementor) sessionFactory);
  241. }
  242. else {
  243. SessionFactory internalFactory = session.getSessionFactory();
  244. if (internalFactory instanceof SessionFactoryImplementor) {
  245. sessionFactoryImpl = (SessionFactoryImplementor) internalFactory;
  246. }
  247. }
  248. return (sessionFactoryImpl != null ? sessionFactoryImpl.getTransactionManager() : null);
  249. }
  250. /**
  251. * Apply the current transaction timeout, if any, to the given
  252. * Hibernate Query object.
  253. * @param query the Hibernate Query object
  254. * @param sessionFactory Hibernate SessionFactory that the Query was created for
  255. */
  256. public static void applyTransactionTimeout(Query query, SessionFactory sessionFactory) {
  257. SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  258. if (sessionHolder != null && sessionHolder.getDeadline() != null) {
  259. query.setTimeout(sessionHolder.getTimeToLiveInSeconds());
  260. }
  261. }
  262. /**
  263. * Apply the current transaction timeout, if any, to the given
  264. * Hibernate Criteria object.
  265. * @param criteria the Hibernate Criteria object
  266. * @param sessionFactory Hibernate SessionFactory that the Criteria was created for
  267. */
  268. public static void applyTransactionTimeout(Criteria criteria, SessionFactory sessionFactory) {
  269. SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  270. if (sessionHolder != null && sessionHolder.getDeadline() != null) {
  271. criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds());
  272. }
  273. }
  274. /**
  275. * Convert the given HibernateException to an appropriate exception from the
  276. * org.springframework.dao hierarchy. Note that it is advisable to handle JDBCException
  277. * specifically by using an SQLExceptionTranslator for the underlying SQLException.
  278. * @param ex HibernateException that occured
  279. * @return the corresponding DataAccessException instance
  280. * @see HibernateAccessor#convertHibernateAccessException
  281. * @see HibernateAccessor#convertJdbcAccessException
  282. * @see HibernateTemplate#execute
  283. */
  284. public static DataAccessException convertHibernateAccessException(HibernateException ex) {
  285. if (ex instanceof JDBCException) {
  286. // SQLException during Hibernate access: only passed in here from custom code,
  287. // as HibernateTemplate etc will use SQLExceptionTranslator-based handling
  288. return new HibernateJdbcException((JDBCException) ex);
  289. }
  290. if (ex instanceof UnresolvableObjectException) {
  291. return new HibernateObjectRetrievalFailureException((UnresolvableObjectException) ex);
  292. }
  293. if (ex instanceof ObjectNotFoundException) {
  294. return new HibernateObjectRetrievalFailureException((ObjectNotFoundException) ex);
  295. }
  296. if (ex instanceof ObjectDeletedException) {
  297. return new HibernateObjectRetrievalFailureException((ObjectDeletedException) ex);
  298. }
  299. if (ex instanceof WrongClassException) {
  300. return new HibernateObjectRetrievalFailureException((WrongClassException) ex);
  301. }
  302. if (ex instanceof StaleObjectStateException) {
  303. return new HibernateOptimisticLockingFailureException((StaleObjectStateException) ex);
  304. }
  305. if (ex instanceof QueryException) {
  306. return new HibernateQueryException((QueryException) ex);
  307. }
  308. if (ex instanceof PersistentObjectException) {
  309. return new InvalidDataAccessApiUsageException(ex.getMessage());
  310. }
  311. if (ex instanceof TransientObjectException) {
  312. return new InvalidDataAccessApiUsageException(ex.getMessage());
  313. }
  314. // fallback
  315. return new HibernateSystemException(ex);
  316. }
  317. /**
  318. * Close the given Session, created via the given factory,
  319. * if it isn't bound to the thread.
  320. * @param session Session to close
  321. * @param sessionFactory Hibernate SessionFactory that the Session was created with
  322. * @throws DataAccessResourceFailureException if the Session couldn't be closed
  323. */
  324. public static void closeSessionIfNecessary(Session session, SessionFactory sessionFactory)
  325. throws CleanupFailureDataAccessException {
  326. if (session == null || TransactionSynchronizationManager.hasResource(sessionFactory)) {
  327. return;
  328. }
  329. logger.debug("Closing Hibernate session");
  330. try {
  331. session.close();
  332. }
  333. catch (JDBCException ex) {
  334. // SQLException underneath
  335. throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex.getSQLException());
  336. }
  337. catch (HibernateException ex) {
  338. throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex);
  339. }
  340. }
  341. /**
  342. * Callback for resource cleanup at the end of a Spring-managed JTA transaction,
  343. * i.e. when participating in a JtaTransactionManager transaction.
  344. * @see org.springframework.transaction.jta.JtaTransactionManager
  345. */
  346. private static class SpringSessionSynchronization implements TransactionSynchronization {
  347. private final SessionHolder sessionHolder;
  348. private final SessionFactory sessionFactory;
  349. private final SQLExceptionTranslator jdbcExceptionTranslator;
  350. private final boolean newSession;
  351. /**
  352. * Whether Hibernate has a looked-up JTA TransactionManager that it will
  353. * automatically register CacheSynchronizations with on Session connect.
  354. */
  355. private boolean hibernateTransactionCompletion;
  356. private SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory,
  357. SQLExceptionTranslator jdbcExceptionTranslator, boolean newSession) {
  358. this.sessionHolder = sessionHolder;
  359. this.sessionFactory = sessionFactory;
  360. this.jdbcExceptionTranslator = jdbcExceptionTranslator;
  361. // check whether the SessionFactory has a JTA TransactionManager
  362. this.hibernateTransactionCompletion =
  363. (getJtaTransactionManager(sessionFactory, sessionHolder.getSession()) != null);
  364. this.newSession = newSession;
  365. }
  366. public void suspend() {
  367. TransactionSynchronizationManager.unbindResource(this.sessionFactory);
  368. }
  369. public void resume() {
  370. TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
  371. }
  372. public void beforeCommit(boolean readOnly) throws DataAccessException {
  373. if (!readOnly && !this.sessionHolder.getSession().getFlushMode().equals(FlushMode.NEVER)) {
  374. logger.debug("Flushing Hibernate session on transaction synchronization");
  375. try {
  376. this.sessionHolder.getSession().flush();
  377. }
  378. catch (JDBCException ex) {
  379. if (this.jdbcExceptionTranslator != null) {
  380. throw this.jdbcExceptionTranslator.translate("SessionSynchronization", null, ex.getSQLException());
  381. }
  382. else {
  383. throw new HibernateJdbcException(ex);
  384. }
  385. }
  386. catch (HibernateException ex) {
  387. throw convertHibernateAccessException(ex);
  388. }
  389. }
  390. }
  391. public void beforeCompletion() throws CleanupFailureDataAccessException {
  392. if (this.newSession) {
  393. TransactionSynchronizationManager.unbindResource(this.sessionFactory);
  394. if (this.hibernateTransactionCompletion) {
  395. closeSessionIfNecessary(this.sessionHolder.getSession(), this.sessionFactory);
  396. }
  397. }
  398. }
  399. public void afterCompletion(int status) {
  400. if (!this.hibernateTransactionCompletion) {
  401. Session session = this.sessionHolder.getSession();
  402. if (session instanceof SessionImplementor) {
  403. ((SessionImplementor) session).afterTransactionCompletion(status == STATUS_COMMITTED);
  404. }
  405. if (this.newSession) {
  406. closeSessionIfNecessary(session, this.sessionFactory);
  407. }
  408. }
  409. this.sessionHolder.setSynchronizedWithTransaction(false);
  410. }
  411. }
  412. /**
  413. * Callback for resource cleanup at the end of a non-Spring JTA transaction,
  414. * i.e. when plain JTA or EJB CMT is used without Spring's JtaTransactionManager.
  415. */
  416. private static class JtaSessionSynchronization implements Synchronization {
  417. private final SpringSessionSynchronization springSessionSynchronization;
  418. private final TransactionManager jtaTransactionManager;
  419. private JtaSessionSynchronization(SpringSessionSynchronization springSessionSynchronization,
  420. TransactionManager jtaTransactionManager) {
  421. this.springSessionSynchronization = springSessionSynchronization;
  422. this.jtaTransactionManager = jtaTransactionManager;
  423. }
  424. /**
  425. * JTA beforeCompletion callback: just invoked on commit.
  426. * <p>In case of an exception, the JTA transaction gets set to rollback-only.
  427. * (Synchronization.beforeCompletion is not supposed to throw an exception.)
  428. * @see SpringSessionSynchronization#beforeCommit
  429. */
  430. public void beforeCompletion() {
  431. try {
  432. this.springSessionSynchronization.beforeCommit(false);
  433. }
  434. catch (Throwable ex) {
  435. logger.error("beforeCommit callback threw exception", ex);
  436. try {
  437. this.jtaTransactionManager.setRollbackOnly();
  438. }
  439. catch (SystemException ex2) {
  440. logger.error("Could not set JTA transaction rollback-only", ex2);
  441. }
  442. }
  443. }
  444. /**
  445. * JTA afterCompletion callback: invoked after commit/rollback.
  446. * <p>Needs to invoke SpringSessionSynchronization's beforeCompletion
  447. * at this late stage, as there's no corresponding callback with JTA.
  448. * @see SpringSessionSynchronization#beforeCompletion
  449. * @see SpringSessionSynchronization#afterCompletion
  450. */
  451. public void afterCompletion(int status) {
  452. // unbind the SessionHolder from the thread
  453. this.springSessionSynchronization.beforeCompletion();
  454. // just reset the synchronizedWithTransaction flag
  455. this.springSessionSynchronization.afterCompletion(-1);
  456. }
  457. }
  458. }