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 java.sql.Connection;
  18. import java.sql.SQLException;
  19. import javax.sql.DataSource;
  20. import net.sf.hibernate.FlushMode;
  21. import net.sf.hibernate.HibernateException;
  22. import net.sf.hibernate.Interceptor;
  23. import net.sf.hibernate.JDBCException;
  24. import net.sf.hibernate.Session;
  25. import net.sf.hibernate.SessionFactory;
  26. import net.sf.hibernate.connection.ConnectionProvider;
  27. import net.sf.hibernate.engine.SessionFactoryImplementor;
  28. import org.springframework.beans.factory.InitializingBean;
  29. import org.springframework.dao.CleanupFailureDataAccessException;
  30. import org.springframework.dao.DataAccessException;
  31. import org.springframework.jdbc.datasource.ConnectionHolder;
  32. import org.springframework.jdbc.support.SQLExceptionTranslator;
  33. import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
  34. import org.springframework.transaction.CannotCreateTransactionException;
  35. import org.springframework.transaction.TransactionDefinition;
  36. import org.springframework.transaction.TransactionSystemException;
  37. import org.springframework.transaction.support.AbstractPlatformTransactionManager;
  38. import org.springframework.transaction.support.DefaultTransactionStatus;
  39. import org.springframework.transaction.support.TransactionSynchronizationManager;
  40. /**
  41. * PlatformTransactionManager implementation for single Hibernate session factories.
  42. * Binds a Hibernate Session from the specified factory to the thread, potentially
  43. * allowing for one thread Session per factory. SessionFactoryUtils and
  44. * HibernateTemplate are aware of thread-bound Sessions and participate in such
  45. * transactions automatically. Using either is required for Hibernate access code
  46. * that needs to support this transaction handling mechanism.
  47. *
  48. * <p>Supports custom isolation levels, and timeouts that get applied as appropriate
  49. * Hibernate query timeouts. To support the latter, application code must either use
  50. * HibernateTemplate.find or call SessionFactoryUtils' applyTransactionTimeout
  51. * method for each created Hibernate Query object.
  52. *
  53. * <p>This implementation is appropriate for applications that solely use Hibernate
  54. * for transactional data access, but it also supports direct data source access
  55. * within a transaction (i.e. plain JDBC code working with the same DataSource).
  56. * This allows for mixing services that access Hibernate (including transactional
  57. * caching) and services that use plain JDBC (without being aware of Hibernate)!
  58. * Application code needs to stick to the same simple Connection lookup pattern as
  59. * with DataSourceTransactionManager (i.e. DataSourceUtils.getConnection).
  60. *
  61. * <p>Note that to be able to register a DataSource's Connection for plain JDBC
  62. * code, this instance needs to be aware of the DataSource (see setDataSource).
  63. * The given DataSource should obviously match the one used by the given
  64. * SessionFactory. To achieve this, configure both to the same JNDI DataSource,
  65. * or preferably create the SessionFactory with LocalSessionFactoryBean and
  66. * a local DataSource (which will be auto-detected by this transaction manager).
  67. * In the latter case, the Hibernate settings do not have to define a connection
  68. * provider at all, avoiding duplicated configuration.
  69. *
  70. * <p>JTA respectively JtaTransactionManager is necessary for accessing multiple
  71. * transactional resources. The DataSource that Hibernate uses needs to be JTA-enabled
  72. * then (see container setup), alternatively the Hibernate JCA connector can be used
  73. * for direct container integration. Normally, JTA setup for Hibernate is somewhat
  74. * container-specific due to the JTA TransactionManager lookup, required for proper
  75. * transactional handling of the SessionFactory-level read-write cache. Using the
  76. * JCA Connector can solve this but involves packaging issue and container-specific
  77. * connector deployment.
  78. *
  79. * <p>Fortunately, there is an easier way with Spring: SessionFactoryUtils (and thus
  80. * HibernateTemplate) registers synchronizations with TransactionSynchronizationManager
  81. * (as used by JtaTransactionManager), for proper afterCompletion callbacks. Therefore,
  82. * as long as Spring's JtaTransactionManager drives the JTA transactions, Hibernate
  83. * does not require any special configuration for proper JTA participation.
  84. * Note that there are special cases with EJB CMT and restrictive JTA subsystems:
  85. * See JtaTransactionManager's javadoc for details.
  86. *
  87. * <p>Note: Spring's Hibernate support requires Hibernate 2.1 (as of Spring 1.0).
  88. *
  89. * @author Juergen Hoeller
  90. * @since 02.05.2003
  91. * @see #setSessionFactory
  92. * @see #setDataSource
  93. * @see SessionFactoryUtils#getSession
  94. * @see SessionFactoryUtils#applyTransactionTimeout
  95. * @see SessionFactoryUtils#closeSessionIfNecessary
  96. * @see HibernateTemplate#execute
  97. * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
  98. * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout
  99. * @see org.springframework.jdbc.datasource.DataSourceUtils#closeConnectionIfNecessary
  100. * @see org.springframework.jdbc.core.JdbcTemplate
  101. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
  102. */
  103. public class HibernateTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean {
  104. private SessionFactory sessionFactory;
  105. private DataSource dataSource;
  106. private Interceptor entityInterceptor;
  107. private SQLExceptionTranslator jdbcExceptionTranslator = new SQLStateSQLExceptionTranslator();
  108. /**
  109. * Create a new HibernateTransactionManager instance.
  110. * A SessionFactory has to be set to be able to use it.
  111. * @see #setSessionFactory
  112. */
  113. public HibernateTransactionManager() {
  114. }
  115. /**
  116. * Create a new HibernateTransactionManager instance.
  117. * @param sessionFactory SessionFactory to manage transactions for
  118. */
  119. public HibernateTransactionManager(SessionFactory sessionFactory) {
  120. this.sessionFactory = sessionFactory;
  121. afterPropertiesSet();
  122. }
  123. /**
  124. * Set the SessionFactory that this instance should manage transactions for.
  125. */
  126. public void setSessionFactory(SessionFactory sessionFactory) {
  127. this.sessionFactory = sessionFactory;
  128. }
  129. /**
  130. * Return the SessionFactory that this instance should manage transactions for.
  131. */
  132. public SessionFactory getSessionFactory() {
  133. return sessionFactory;
  134. }
  135. /**
  136. * Set the JDBC DataSource that this instance should manage transactions for.
  137. * The DataSource should match the one used by the Hibernate SessionFactory:
  138. * for example, you could specify the same JNDI DataSource for both.
  139. * <p>If the SessionFactory was configured with LocalDataSourceConnectionProvider,
  140. * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource",
  141. * the DataSource will be auto-detected: You can still explictly specify the
  142. * DataSource, but you don't need to in this case.
  143. * <p>A transactional JDBC Connection for this DataSource will be provided to
  144. * application code accessing this DataSource directly via DataSourceUtils
  145. * or JdbcTemplate. The Connection will be taken from the Hibernate Session.
  146. * @see LocalDataSourceConnectionProvider
  147. * @see LocalSessionFactoryBean#setDataSource
  148. */
  149. public void setDataSource(DataSource dataSource) {
  150. this.dataSource = dataSource;
  151. }
  152. /**
  153. * Return the JDBC DataSource that this instance manages transactions for.
  154. */
  155. public DataSource getDataSource() {
  156. return dataSource;
  157. }
  158. /**
  159. * Set a Hibernate entity interceptor that allows to inspect and change
  160. * property values before writing to and reading from the database.
  161. * Will get applied to any new Session created by this transaction manager.
  162. * <p>Such an interceptor can either be set at the SessionFactory level,
  163. * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
  164. * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
  165. * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
  166. * to avoid repeated configuration and guarantee consistent behavior in transactions.
  167. * @see LocalSessionFactoryBean#setEntityInterceptor
  168. * @see HibernateTemplate#setEntityInterceptor
  169. * @see HibernateInterceptor#setEntityInterceptor
  170. */
  171. public void setEntityInterceptor(Interceptor entityInterceptor) {
  172. this.entityInterceptor = entityInterceptor;
  173. }
  174. /**
  175. * Return the current Hibernate entity interceptor, or null if none.
  176. */
  177. public Interceptor getEntityInterceptor() {
  178. return entityInterceptor;
  179. }
  180. /**
  181. * Set the JDBC exception translator for this transaction manager.
  182. * Applied to SQLExceptions (wrapped by Hibernate's JDBCException)
  183. * thrown by flushing on commit.
  184. * <p>The default exception translator evaluates the exception's SQLState.
  185. * @param jdbcExceptionTranslator exception translator
  186. * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
  187. * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
  188. */
  189. public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
  190. this.jdbcExceptionTranslator = jdbcExceptionTranslator;
  191. }
  192. /**
  193. * Return the JDBC exception translator for this transaction manager.
  194. */
  195. public SQLExceptionTranslator getJdbcExceptionTranslator() {
  196. return this.jdbcExceptionTranslator;
  197. }
  198. public void afterPropertiesSet() {
  199. if (this.sessionFactory == null) {
  200. throw new IllegalArgumentException("sessionFactory is required");
  201. }
  202. // check for LocalDataSourceConnectionProvider
  203. if (this.sessionFactory instanceof SessionFactoryImplementor) {
  204. ConnectionProvider cp = ((SessionFactoryImplementor) this.sessionFactory).getConnectionProvider();
  205. if (cp instanceof LocalDataSourceConnectionProvider) {
  206. DataSource cpds = ((LocalDataSourceConnectionProvider) cp).getDataSource();
  207. if (this.dataSource == null) {
  208. // use the SessionFactory's DataSource for exposing transactions to JDBC code
  209. logger.info("Using DataSource [" + cpds + "] from Hibernate SessionFactory for HibernateTransactionManager");
  210. this.dataSource = cpds;
  211. }
  212. else if (this.dataSource == cpds) {
  213. // let the configuration through: it's consistent
  214. }
  215. else {
  216. throw new IllegalArgumentException("Specified dataSource [" + this.dataSource +
  217. "] does not match [" + cpds + "] used by the SessionFactory");
  218. }
  219. }
  220. }
  221. }
  222. protected Object doGetTransaction() {
  223. if (TransactionSynchronizationManager.hasResource(this.sessionFactory)) {
  224. SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(this.sessionFactory);
  225. logger.debug("Found thread-bound session [" + sessionHolder.getSession() + "] for Hibernate transaction");
  226. return new HibernateTransactionObject(sessionHolder);
  227. }
  228. else {
  229. return new HibernateTransactionObject();
  230. }
  231. }
  232. protected boolean isExistingTransaction(Object transaction) {
  233. return ((HibernateTransactionObject) transaction).hasTransaction();
  234. }
  235. protected void doBegin(Object transaction, TransactionDefinition definition) {
  236. HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  237. // cache to avoid repeated checks
  238. boolean debugEnabled = logger.isDebugEnabled();
  239. if (txObject.getSessionHolder() == null) {
  240. Session session = SessionFactoryUtils.getSession(this.sessionFactory, this.entityInterceptor,
  241. this.jdbcExceptionTranslator, false);
  242. if (debugEnabled) {
  243. logger.debug("Opened new session [" + session + "] for Hibernate transaction");
  244. }
  245. txObject.setSessionHolder(new SessionHolder(session));
  246. }
  247. try {
  248. txObject.getSessionHolder().setSynchronizedWithTransaction(true);
  249. Session session = txObject.getSessionHolder().getSession();
  250. if (debugEnabled) {
  251. logger.debug("Beginning Hibernate transaction on session [" + session + "]");
  252. }
  253. // apply read-only
  254. if (definition.isReadOnly()) {
  255. if (txObject.isNewSessionHolder()) {
  256. // just set to NEVER in case of a new Session for this transaction
  257. session.setFlushMode(FlushMode.NEVER);
  258. }
  259. try {
  260. Connection con = session.connection();
  261. if (debugEnabled) {
  262. logger.debug("Setting JDBC connection [" + con + "] read-only");
  263. }
  264. con.setReadOnly(true);
  265. }
  266. catch (Exception ex) {
  267. // SQLException or UnsupportedOperationException
  268. logger.warn("Could not set JDBC connection read-only", ex);
  269. }
  270. }
  271. else if (!txObject.isNewSessionHolder()) {
  272. // we need AUTO or COMMIT for a non-read-only transaction
  273. FlushMode flushMode = session.getFlushMode();
  274. if (FlushMode.NEVER.equals(flushMode)) {
  275. txObject.setPreviousFlushMode(flushMode);
  276. session.setFlushMode(FlushMode.AUTO);
  277. }
  278. }
  279. // apply isolation level
  280. if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
  281. Connection con = session.connection();
  282. if (debugEnabled) {
  283. logger.debug("Changing isolation level of JDBC connection [" + con + "] to " +
  284. definition.getIsolationLevel());
  285. }
  286. txObject.setPreviousIsolationLevel(new Integer(con.getTransactionIsolation()));
  287. session.connection().setTransactionIsolation(definition.getIsolationLevel());
  288. }
  289. // add the Hibernate transaction to the session holder
  290. txObject.getSessionHolder().setTransaction(session.beginTransaction());
  291. // register transaction timeout
  292. if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
  293. txObject.getSessionHolder().setTimeoutInSeconds(definition.getTimeout());
  294. }
  295. // bind the session holder to the thread
  296. if (txObject.isNewSessionHolder()) {
  297. TransactionSynchronizationManager.bindResource(this.sessionFactory, txObject.getSessionHolder());
  298. }
  299. // register the Hibernate Session's JDBC Connection for the DataSource, if set
  300. if (this.dataSource != null) {
  301. ConnectionHolder conHolder = new ConnectionHolder(session.connection());
  302. if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
  303. conHolder.setTimeoutInSeconds(definition.getTimeout());
  304. }
  305. TransactionSynchronizationManager.bindResource(this.dataSource, conHolder);
  306. }
  307. }
  308. catch (SQLException ex) {
  309. throw new CannotCreateTransactionException("Could not set transaction isolation", ex);
  310. }
  311. catch (HibernateException ex) {
  312. throw new CannotCreateTransactionException("Could not create Hibernate transaction", ex);
  313. }
  314. }
  315. protected Object doSuspend(Object transaction) {
  316. HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  317. txObject.setSessionHolder(null);
  318. SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(this.sessionFactory);
  319. ConnectionHolder connectionHolder = null;
  320. if (this.dataSource != null) {
  321. connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(this.dataSource);
  322. }
  323. return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
  324. }
  325. protected void doResume(Object transaction, Object suspendedResources) {
  326. SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
  327. if (TransactionSynchronizationManager.hasResource(this.sessionFactory)) {
  328. // from non-transactional code running in active transaction synchronization
  329. // -> can be safely removed, will be closed on transaction completion
  330. TransactionSynchronizationManager.unbindResource(this.sessionFactory);
  331. }
  332. TransactionSynchronizationManager.bindResource(this.sessionFactory, resourcesHolder.getSessionHolder());
  333. if (this.dataSource != null) {
  334. TransactionSynchronizationManager.bindResource(this.dataSource, resourcesHolder.getConnectionHolder());
  335. }
  336. }
  337. protected boolean isRollbackOnly(Object transaction) {
  338. return ((HibernateTransactionObject) transaction).getSessionHolder().isRollbackOnly();
  339. }
  340. protected void doCommit(DefaultTransactionStatus status) {
  341. HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
  342. if (status.isDebug()) {
  343. logger.debug("Committing Hibernate transaction on session [" +
  344. txObject.getSessionHolder().getSession() + "]");
  345. }
  346. try {
  347. txObject.getSessionHolder().getTransaction().commit();
  348. }
  349. catch (net.sf.hibernate.TransactionException ex) {
  350. // assumably from commit call to underlying JDBC connection
  351. throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
  352. }
  353. catch (JDBCException ex) {
  354. // assumably failed to flush changes to database
  355. throw convertJdbcAccessException(ex.getSQLException());
  356. }
  357. catch (HibernateException ex) {
  358. // assumably failed to flush changes to database
  359. throw convertHibernateAccessException(ex);
  360. }
  361. }
  362. protected void doRollback(DefaultTransactionStatus status) {
  363. HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
  364. if (status.isDebug()) {
  365. logger.debug("Rolling back Hibernate transaction on session [" +
  366. txObject.getSessionHolder().getSession() + "]");
  367. }
  368. try {
  369. txObject.getSessionHolder().getTransaction().rollback();
  370. }
  371. catch (net.sf.hibernate.TransactionException ex) {
  372. throw new TransactionSystemException("Could not rollback Hibernate transaction", ex);
  373. }
  374. catch (JDBCException ex) {
  375. // shouldn't really happen, as a rollback doesn't cause a flush
  376. throw convertJdbcAccessException(ex.getSQLException());
  377. }
  378. catch (HibernateException ex) {
  379. // shouldn't really happen, as a rollback doesn't cause a flush
  380. throw convertHibernateAccessException(ex);
  381. }
  382. }
  383. protected void doSetRollbackOnly(DefaultTransactionStatus status) {
  384. HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
  385. if (status.isDebug()) {
  386. logger.debug("Setting Hibernate transaction on Session [" +
  387. txObject.getSessionHolder().getSession() + "] rollback-only");
  388. }
  389. txObject.getSessionHolder().setRollbackOnly();
  390. }
  391. protected void doCleanupAfterCompletion(Object transaction) {
  392. HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  393. // remove the JDBC connection holder from the thread, if set
  394. if (this.dataSource != null) {
  395. TransactionSynchronizationManager.unbindResource(this.dataSource);
  396. }
  397. // remove the session holder from the thread
  398. if (txObject.isNewSessionHolder()) {
  399. TransactionSynchronizationManager.unbindResource(this.sessionFactory);
  400. }
  401. try {
  402. Connection con = txObject.getSessionHolder().getSession().connection();
  403. // reset transaction isolation to previous value, if changed for the transaction
  404. if (txObject.getPreviousIsolationLevel() != null) {
  405. if (logger.isDebugEnabled()) {
  406. logger.debug("Resetting isolation level of connection [" + con + "] to " +
  407. txObject.getPreviousIsolationLevel());
  408. }
  409. con.setTransactionIsolation(txObject.getPreviousIsolationLevel().intValue());
  410. }
  411. // reset read-only
  412. if (con.isReadOnly()) {
  413. if (logger.isDebugEnabled()) {
  414. logger.debug("Resetting read-only flag of connection [" + con + "]");
  415. }
  416. con.setReadOnly(false);
  417. }
  418. }
  419. catch (Exception ex) {
  420. // HibernateException, SQLException, or UnsupportedOperationException
  421. // typically not something to worry about, can be ignored
  422. logger.info("Could not reset JDBC connection of Hibernate session", ex);
  423. }
  424. Session session = txObject.getSessionHolder().getSession();
  425. if (txObject.isNewSessionHolder()) {
  426. if (logger.isDebugEnabled()) {
  427. logger.debug("Closing Hibernate session [" + session + "] after transaction");
  428. }
  429. try {
  430. SessionFactoryUtils.closeSessionIfNecessary(session, this.sessionFactory);
  431. }
  432. catch (CleanupFailureDataAccessException ex) {
  433. // just log it, to keep a transaction-related exception
  434. logger.error("Count not close Hibernate session after transaction", ex);
  435. }
  436. }
  437. else {
  438. if (logger.isDebugEnabled()) {
  439. logger.debug("Not closing pre-bound Hibernate session [" + session + "] after transaction");
  440. }
  441. txObject.getSessionHolder().setTransaction(null);
  442. if (txObject.getPreviousFlushMode() != null) {
  443. session.setFlushMode(txObject.getPreviousFlushMode());
  444. }
  445. }
  446. }
  447. /**
  448. * Convert the given HibernateException to an appropriate exception from
  449. * the org.springframework.dao hierarchy. Can be overridden in subclasses.
  450. * @param ex HibernateException that occured
  451. * @return the corresponding DataAccessException instance
  452. */
  453. protected DataAccessException convertHibernateAccessException(HibernateException ex) {
  454. return SessionFactoryUtils.convertHibernateAccessException(ex);
  455. }
  456. /**
  457. * Convert the given SQLException to an appropriate exception from the
  458. * org.springframework.dao hierarchy. Uses a JDBC exception translater if set,
  459. * and a generic HibernateJdbcException else. Can be overridden in subclasses.
  460. * @param ex SQLException that occured
  461. * @return the corresponding DataAccessException instance
  462. * @see #setJdbcExceptionTranslator
  463. */
  464. protected DataAccessException convertJdbcAccessException(SQLException ex) {
  465. if (this.jdbcExceptionTranslator != null) {
  466. return this.jdbcExceptionTranslator.translate("HibernateTemplate", null, ex);
  467. }
  468. else {
  469. return new HibernateJdbcException(ex);
  470. }
  471. }
  472. /**
  473. * Holder for suspended resources.
  474. * Used internally by doSuspend and doResume.
  475. * @see #doSuspend
  476. * @see #doResume
  477. */
  478. private static class SuspendedResourcesHolder {
  479. private final SessionHolder sessionHolder;
  480. private final ConnectionHolder connectionHolder;
  481. private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder connectionHolder) {
  482. this.sessionHolder = sessionHolder;
  483. this.connectionHolder = connectionHolder;
  484. }
  485. private SessionHolder getSessionHolder() {
  486. return sessionHolder;
  487. }
  488. private ConnectionHolder getConnectionHolder() {
  489. return connectionHolder;
  490. }
  491. }
  492. }