001/* 002 * Copyright 2002-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.orm.hibernate4; 018 019import java.sql.Connection; 020import java.sql.ResultSet; 021import javax.sql.DataSource; 022 023import org.hibernate.ConnectionReleaseMode; 024import org.hibernate.FlushMode; 025import org.hibernate.HibernateException; 026import org.hibernate.Interceptor; 027import org.hibernate.Session; 028import org.hibernate.SessionFactory; 029import org.hibernate.Transaction; 030import org.hibernate.engine.spi.SessionImplementor; 031import org.hibernate.engine.transaction.spi.TransactionContext; 032 033import org.springframework.beans.BeansException; 034import org.springframework.beans.factory.BeanFactory; 035import org.springframework.beans.factory.BeanFactoryAware; 036import org.springframework.beans.factory.InitializingBean; 037import org.springframework.dao.DataAccessException; 038import org.springframework.dao.DataAccessResourceFailureException; 039import org.springframework.jdbc.datasource.ConnectionHolder; 040import org.springframework.jdbc.datasource.DataSourceUtils; 041import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport; 042import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; 043import org.springframework.transaction.CannotCreateTransactionException; 044import org.springframework.transaction.IllegalTransactionStateException; 045import org.springframework.transaction.InvalidIsolationLevelException; 046import org.springframework.transaction.TransactionDefinition; 047import org.springframework.transaction.TransactionSystemException; 048import org.springframework.transaction.support.AbstractPlatformTransactionManager; 049import org.springframework.transaction.support.DefaultTransactionStatus; 050import org.springframework.transaction.support.ResourceTransactionManager; 051import org.springframework.transaction.support.TransactionSynchronizationManager; 052 053/** 054 * {@link org.springframework.transaction.PlatformTransactionManager} 055 * implementation for a single Hibernate {@link org.hibernate.SessionFactory}. 056 * Binds a Hibernate Session from the specified factory to the thread, 057 * potentially allowing for one thread-bound Session per factory. 058 * {@code SessionFactory.getCurrentSession()} is required for Hibernate 059 * access code that needs to support this transaction handling mechanism, 060 * with the SessionFactory being configured with {@link SpringSessionContext}. 061 * 062 * <p>Supports custom isolation levels, and timeouts that get applied as 063 * Hibernate transaction timeouts. 064 * 065 * <p>This transaction manager is appropriate for applications that use a single 066 * Hibernate SessionFactory for transactional data access, but it also supports 067 * direct DataSource access within a transaction (i.e. plain JDBC code working 068 * with the same DataSource). This allows for mixing services which access Hibernate 069 * and services which use plain JDBC (without being aware of Hibernate)! 070 * Application code needs to stick to the same simple Connection lookup pattern as 071 * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} 072 * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection} 073 * or going through a 074 * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}). 075 * 076 * <p>Note: To be able to register a DataSource's Connection for plain JDBC code, 077 * this instance needs to be aware of the DataSource ({@link #setDataSource}). 078 * The given DataSource should obviously match the one used by the given SessionFactory. 079 * 080 * <p>JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager}) 081 * is necessary for accessing multiple transactional resources within the same 082 * transaction. The DataSource that Hibernate uses needs to be JTA-enabled in 083 * such a scenario (see container setup). 084 * 085 * <p>This transaction manager supports nested transactions via JDBC 3.0 Savepoints. 086 * The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"} flag defaults 087 * to "false", though, as nested transactions will just apply to the JDBC Connection, 088 * not to the Hibernate Session and its cached entity objects and related context. 089 * You can manually set the flag to "true" if you want to use nested transactions 090 * for JDBC access code which participates in Hibernate transactions (provided that 091 * your JDBC driver supports Savepoints). <i>Note that Hibernate itself does not 092 * support nested transactions! Hence, do not expect Hibernate access code to 093 * semantically participate in a nested transaction.</i> 094 * 095 * <p><b>NOTE: Hibernate 4.2+ is strongly recommended for efficient transaction 096 * management with Spring, in particular for transactional Spring JDBC access.</b> 097 * 098 * @author Juergen Hoeller 099 * @since 3.1 100 * @see #setSessionFactory 101 * @see #setDataSource 102 * @see org.hibernate.SessionFactory#getCurrentSession() 103 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection 104 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection 105 * @see org.springframework.jdbc.core.JdbcTemplate 106 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager 107 * @see org.springframework.transaction.jta.JtaTransactionManager 108 */ 109@SuppressWarnings("serial") 110public class HibernateTransactionManager extends AbstractPlatformTransactionManager 111 implements ResourceTransactionManager, BeanFactoryAware, InitializingBean { 112 113 private SessionFactory sessionFactory; 114 115 private DataSource dataSource; 116 117 private boolean autodetectDataSource = true; 118 119 private boolean prepareConnection = true; 120 121 private boolean allowResultAccessAfterCompletion = false; 122 123 private boolean hibernateManagedSession = false; 124 125 private Object entityInterceptor; 126 127 /** 128 * Just needed for entityInterceptorBeanName. 129 * @see #setEntityInterceptorBeanName 130 */ 131 private BeanFactory beanFactory; 132 133 134 /** 135 * Create a new HibernateTransactionManager instance. 136 * A SessionFactory has to be set to be able to use it. 137 * @see #setSessionFactory 138 */ 139 public HibernateTransactionManager() { 140 } 141 142 /** 143 * Create a new HibernateTransactionManager instance. 144 * @param sessionFactory SessionFactory to manage transactions for 145 */ 146 public HibernateTransactionManager(SessionFactory sessionFactory) { 147 this.sessionFactory = sessionFactory; 148 afterPropertiesSet(); 149 } 150 151 152 /** 153 * Set the SessionFactory that this instance should manage transactions for. 154 */ 155 public void setSessionFactory(SessionFactory sessionFactory) { 156 this.sessionFactory = sessionFactory; 157 } 158 159 /** 160 * Return the SessionFactory that this instance should manage transactions for. 161 */ 162 public SessionFactory getSessionFactory() { 163 return this.sessionFactory; 164 } 165 166 /** 167 * Set the JDBC DataSource that this instance should manage transactions for. 168 * The DataSource should match the one used by the Hibernate SessionFactory: 169 * for example, you could specify the same JNDI DataSource for both. 170 * <p>If the SessionFactory was configured with LocalDataSourceConnectionProvider, 171 * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource", 172 * the DataSource will be auto-detected: You can still explicitly specify the 173 * DataSource, but you don't need to in this case. 174 * <p>A transactional JDBC Connection for this DataSource will be provided to 175 * application code accessing this DataSource directly via DataSourceUtils 176 * or JdbcTemplate. The Connection will be taken from the Hibernate Session. 177 * <p>The DataSource specified here should be the target DataSource to manage 178 * transactions for, not a TransactionAwareDataSourceProxy. Only data access 179 * code may work with TransactionAwareDataSourceProxy, while the transaction 180 * manager needs to work on the underlying target DataSource. If there's 181 * nevertheless a TransactionAwareDataSourceProxy passed in, it will be 182 * unwrapped to extract its target DataSource. 183 * @see #setAutodetectDataSource 184 * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy 185 * @see org.springframework.jdbc.datasource.DataSourceUtils 186 * @see org.springframework.jdbc.core.JdbcTemplate 187 */ 188 public void setDataSource(DataSource dataSource) { 189 if (dataSource instanceof TransactionAwareDataSourceProxy) { 190 // If we got a TransactionAwareDataSourceProxy, we need to perform transactions 191 // for its underlying target DataSource, else data access code won't see 192 // properly exposed transactions (i.e. transactions for the target DataSource). 193 this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); 194 } 195 else { 196 this.dataSource = dataSource; 197 } 198 } 199 200 /** 201 * Return the JDBC DataSource that this instance manages transactions for. 202 */ 203 public DataSource getDataSource() { 204 return this.dataSource; 205 } 206 207 /** 208 * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory, 209 * if set via LocalSessionFactoryBean's {@code setDataSource}. Default is "true". 210 * <p>Can be turned off to deliberately ignore an available DataSource, in order 211 * to not expose Hibernate transactions as JDBC transactions for that DataSource. 212 * @see #setDataSource 213 */ 214 public void setAutodetectDataSource(boolean autodetectDataSource) { 215 this.autodetectDataSource = autodetectDataSource; 216 } 217 218 /** 219 * Set whether to prepare the underlying JDBC Connection of a transactional 220 * Hibernate Session, that is, whether to apply a transaction-specific 221 * isolation level and/or the transaction's read-only flag to the underlying 222 * JDBC Connection. 223 * <p>Default is "true". If you turn this flag off, the transaction manager 224 * will not support per-transaction isolation levels anymore. It will not 225 * call {@code Connection.setReadOnly(true)} for read-only transactions 226 * anymore either. If this flag is turned off, no cleanup of a JDBC Connection 227 * is required after a transaction, since no Connection settings will get modified. 228 * @see java.sql.Connection#setTransactionIsolation 229 * @see java.sql.Connection#setReadOnly 230 */ 231 public void setPrepareConnection(boolean prepareConnection) { 232 this.prepareConnection = prepareConnection; 233 } 234 235 /** 236 * Set whether to allow result access after completion, typically via Hibernate's 237 * ScrollableResults mechanism. 238 * <p>Default is "false". Turning this flag on enforces over-commit holdability on the 239 * underlying JDBC Connection (if {@link #prepareConnection "prepareConnection"} is on) 240 * and skips the disconnect-on-completion step. 241 * @since 4.1.2 242 * @see java.sql.Connection#setHoldability 243 * @see ResultSet#HOLD_CURSORS_OVER_COMMIT 244 * @see #disconnectOnCompletion(Session) 245 */ 246 public void setAllowResultAccessAfterCompletion(boolean allowResultAccessAfterCompletion) { 247 this.allowResultAccessAfterCompletion = allowResultAccessAfterCompletion; 248 } 249 250 /** 251 * Set whether to operate on a Hibernate-managed Session instead of a 252 * Spring-managed Session, that is, whether to obtain the Session through 253 * Hibernate's {@link org.hibernate.SessionFactory#getCurrentSession()} 254 * instead of {@link org.hibernate.SessionFactory#openSession()} (with a Spring 255 * {@link org.springframework.transaction.support.TransactionSynchronizationManager} 256 * check preceding it). 257 * <p>Default is "false", i.e. using a Spring-managed Session: taking the current 258 * thread-bound Session if available (e.g. in an Open-Session-in-View scenario), 259 * creating a new Session for the current transaction otherwise. 260 * <p>Switch this flag to "true" in order to enforce use of a Hibernate-managed Session. 261 * Note that this requires {@link org.hibernate.SessionFactory#getCurrentSession()} 262 * to always return a proper Session when called for a Spring-managed transaction; 263 * transaction begin will fail if the {@code getCurrentSession()} call fails. 264 * <p>This mode will typically be used in combination with a custom Hibernate 265 * {@link org.hibernate.context.spi.CurrentSessionContext} implementation that stores 266 * Sessions in a place other than Spring's TransactionSynchronizationManager. 267 * It may also be used in combination with Spring's Open-Session-in-View support 268 * (using Spring's default {@link SpringSessionContext}), in which case it subtly 269 * differs from the Spring-managed Session mode: The pre-bound Session will <i>not</i> 270 * receive a {@code clear()} call (on rollback) or a {@code disconnect()} 271 * call (on transaction completion) in such a scenario; this is rather left up 272 * to a custom CurrentSessionContext implementation (if desired). 273 */ 274 public void setHibernateManagedSession(boolean hibernateManagedSession) { 275 this.hibernateManagedSession = hibernateManagedSession; 276 } 277 278 /** 279 * Set the bean name of a Hibernate entity interceptor that allows to inspect 280 * and change property values before writing to and reading from the database. 281 * Will get applied to any new Session created by this transaction manager. 282 * <p>Requires the bean factory to be known, to be able to resolve the bean 283 * name to an interceptor instance on session creation. Typically used for 284 * prototype interceptors, i.e. a new interceptor instance per session. 285 * <p>Can also be used for shared interceptor instances, but it is recommended 286 * to set the interceptor reference directly in such a scenario. 287 * @param entityInterceptorBeanName the name of the entity interceptor in 288 * the bean factory 289 * @see #setBeanFactory 290 * @see #setEntityInterceptor 291 */ 292 public void setEntityInterceptorBeanName(String entityInterceptorBeanName) { 293 this.entityInterceptor = entityInterceptorBeanName; 294 } 295 296 /** 297 * Set a Hibernate entity interceptor that allows to inspect and change 298 * property values before writing to and reading from the database. 299 * Will get applied to any new Session created by this transaction manager. 300 * <p>Such an interceptor can either be set at the SessionFactory level, 301 * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on 302 * HibernateTransactionManager. 303 * @see LocalSessionFactoryBean#setEntityInterceptor 304 */ 305 public void setEntityInterceptor(Interceptor entityInterceptor) { 306 this.entityInterceptor = entityInterceptor; 307 } 308 309 /** 310 * Return the current Hibernate entity interceptor, or {@code null} if none. 311 * Resolves an entity interceptor bean name via the bean factory, 312 * if necessary. 313 * @throws IllegalStateException if bean name specified but no bean factory set 314 * @throws BeansException if bean name resolution via the bean factory failed 315 * @see #setEntityInterceptor 316 * @see #setEntityInterceptorBeanName 317 * @see #setBeanFactory 318 */ 319 public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException { 320 if (this.entityInterceptor instanceof Interceptor) { 321 return (Interceptor) entityInterceptor; 322 } 323 else if (this.entityInterceptor instanceof String) { 324 if (this.beanFactory == null) { 325 throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set"); 326 } 327 String beanName = (String) this.entityInterceptor; 328 return this.beanFactory.getBean(beanName, Interceptor.class); 329 } 330 else { 331 return null; 332 } 333 } 334 335 /** 336 * The bean factory just needs to be known for resolving entity interceptor 337 * bean names. It does not need to be set for any other mode of operation. 338 * @see #setEntityInterceptorBeanName 339 */ 340 @Override 341 public void setBeanFactory(BeanFactory beanFactory) { 342 this.beanFactory = beanFactory; 343 } 344 345 @Override 346 public void afterPropertiesSet() { 347 if (getSessionFactory() == null) { 348 throw new IllegalArgumentException("Property 'sessionFactory' is required"); 349 } 350 if (this.entityInterceptor instanceof String && this.beanFactory == null) { 351 throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'"); 352 } 353 354 // Check for SessionFactory's DataSource. 355 if (this.autodetectDataSource && getDataSource() == null) { 356 DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory()); 357 if (sfds != null) { 358 // Use the SessionFactory's DataSource for exposing transactions to JDBC code. 359 if (logger.isInfoEnabled()) { 360 logger.info("Using DataSource [" + sfds + 361 "] of Hibernate SessionFactory for HibernateTransactionManager"); 362 } 363 setDataSource(sfds); 364 } 365 } 366 } 367 368 369 @Override 370 public Object getResourceFactory() { 371 return getSessionFactory(); 372 } 373 374 @Override 375 protected Object doGetTransaction() { 376 HibernateTransactionObject txObject = new HibernateTransactionObject(); 377 txObject.setSavepointAllowed(isNestedTransactionAllowed()); 378 379 SessionHolder sessionHolder = 380 (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory()); 381 if (sessionHolder != null) { 382 if (logger.isDebugEnabled()) { 383 logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction"); 384 } 385 txObject.setSessionHolder(sessionHolder); 386 } 387 else if (this.hibernateManagedSession) { 388 try { 389 Session session = this.sessionFactory.getCurrentSession(); 390 if (logger.isDebugEnabled()) { 391 logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction"); 392 } 393 txObject.setExistingSession(session); 394 } 395 catch (HibernateException ex) { 396 throw new DataAccessResourceFailureException( 397 "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex); 398 } 399 } 400 401 if (getDataSource() != null) { 402 ConnectionHolder conHolder = (ConnectionHolder) 403 TransactionSynchronizationManager.getResource(getDataSource()); 404 txObject.setConnectionHolder(conHolder); 405 } 406 407 return txObject; 408 } 409 410 @Override 411 protected boolean isExistingTransaction(Object transaction) { 412 HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; 413 return (txObject.hasSpringManagedTransaction() || 414 (this.hibernateManagedSession && txObject.hasHibernateManagedTransaction())); 415 } 416 417 @Override 418 protected void doBegin(Object transaction, TransactionDefinition definition) { 419 HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; 420 421 if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) { 422 throw new IllegalTransactionStateException( 423 "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " + 424 "running within DataSourceTransactionManager if told to manage the DataSource itself. " + 425 "It is recommended to use a single HibernateTransactionManager for all transactions " + 426 "on a single DataSource, no matter whether Hibernate or JDBC access."); 427 } 428 429 Session session = null; 430 431 try { 432 if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) { 433 Interceptor entityInterceptor = getEntityInterceptor(); 434 Session newSession = (entityInterceptor != null ? 435 getSessionFactory().withOptions().interceptor(entityInterceptor).openSession() : 436 getSessionFactory().openSession()); 437 if (logger.isDebugEnabled()) { 438 logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction"); 439 } 440 txObject.setSession(newSession); 441 } 442 443 session = txObject.getSessionHolder().getSession(); 444 445 if (this.prepareConnection && isSameConnectionForEntireSession(session)) { 446 // We're allowed to change the transaction settings of the JDBC Connection. 447 if (logger.isDebugEnabled()) { 448 logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]"); 449 } 450 Connection con = ((SessionImplementor) session).connection(); 451 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); 452 txObject.setPreviousIsolationLevel(previousIsolationLevel); 453 if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) { 454 int currentHoldability = con.getHoldability(); 455 if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) { 456 txObject.setPreviousHoldability(currentHoldability); 457 con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); 458 } 459 } 460 } 461 else { 462 // Not allowed to change the transaction settings of the JDBC Connection. 463 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { 464 // We should set a specific isolation level but are not allowed to... 465 throw new InvalidIsolationLevelException( 466 "HibernateTransactionManager is not allowed to support custom isolation levels: " + 467 "make sure that its 'prepareConnection' flag is on (the default) and that the " + 468 "Hibernate connection release mode is set to 'on_close' (the default for JDBC)."); 469 } 470 if (logger.isDebugEnabled()) { 471 logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]"); 472 } 473 } 474 475 if (definition.isReadOnly() && txObject.isNewSession()) { 476 // Just set to MANUAL in case of a new Session for this transaction. 477 session.setFlushMode(FlushMode.MANUAL); 478 } 479 480 if (!definition.isReadOnly() && !txObject.isNewSession()) { 481 // We need AUTO or COMMIT for a non-read-only transaction. 482 FlushMode flushMode = session.getFlushMode(); 483 if (FlushMode.MANUAL.equals(flushMode)) { 484 session.setFlushMode(FlushMode.AUTO); 485 txObject.getSessionHolder().setPreviousFlushMode(flushMode); 486 } 487 } 488 489 Transaction hibTx; 490 491 // Register transaction timeout. 492 int timeout = determineTimeout(definition); 493 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { 494 // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+ 495 // Applies to all statements, also to inserts, updates and deletes! 496 hibTx = session.getTransaction(); 497 hibTx.setTimeout(timeout); 498 hibTx.begin(); 499 } 500 else { 501 // Open a plain Hibernate transaction without specified timeout. 502 hibTx = session.beginTransaction(); 503 } 504 505 // Add the Hibernate transaction to the session holder. 506 txObject.getSessionHolder().setTransaction(hibTx); 507 508 // Register the Hibernate Session's JDBC Connection for the DataSource, if set. 509 if (getDataSource() != null) { 510 Connection con = ((SessionImplementor) session).connection(); 511 ConnectionHolder conHolder = new ConnectionHolder(con); 512 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { 513 conHolder.setTimeoutInSeconds(timeout); 514 } 515 if (logger.isDebugEnabled()) { 516 logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]"); 517 } 518 TransactionSynchronizationManager.bindResource(getDataSource(), conHolder); 519 txObject.setConnectionHolder(conHolder); 520 } 521 522 // Bind the session holder to the thread. 523 if (txObject.isNewSessionHolder()) { 524 TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder()); 525 } 526 txObject.getSessionHolder().setSynchronizedWithTransaction(true); 527 } 528 529 catch (Throwable ex) { 530 if (txObject.isNewSession()) { 531 try { 532 if (session.getTransaction().isActive()) { 533 session.getTransaction().rollback(); 534 } 535 } 536 catch (Throwable ex2) { 537 logger.debug("Could not rollback Session after failed transaction begin", ex); 538 } 539 finally { 540 SessionFactoryUtils.closeSession(session); 541 txObject.setSessionHolder(null); 542 } 543 } 544 throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex); 545 } 546 } 547 548 @Override 549 protected Object doSuspend(Object transaction) { 550 HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; 551 txObject.setSessionHolder(null); 552 SessionHolder sessionHolder = 553 (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory()); 554 txObject.setConnectionHolder(null); 555 ConnectionHolder connectionHolder = null; 556 if (getDataSource() != null) { 557 connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource()); 558 } 559 return new SuspendedResourcesHolder(sessionHolder, connectionHolder); 560 } 561 562 @Override 563 protected void doResume(Object transaction, Object suspendedResources) { 564 SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources; 565 if (TransactionSynchronizationManager.hasResource(getSessionFactory())) { 566 // From non-transactional code running in active transaction synchronization 567 // -> can be safely removed, will be closed on transaction completion. 568 TransactionSynchronizationManager.unbindResource(getSessionFactory()); 569 } 570 TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder()); 571 if (getDataSource() != null) { 572 TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder()); 573 } 574 } 575 576 @Override 577 protected void doCommit(DefaultTransactionStatus status) { 578 HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); 579 if (status.isDebug()) { 580 logger.debug("Committing Hibernate transaction on Session [" + 581 txObject.getSessionHolder().getSession() + "]"); 582 } 583 try { 584 txObject.getSessionHolder().getTransaction().commit(); 585 } 586 catch (org.hibernate.TransactionException ex) { 587 // assumably from commit call to the underlying JDBC connection 588 throw new TransactionSystemException("Could not commit Hibernate transaction", ex); 589 } 590 catch (HibernateException ex) { 591 // assumably failed to flush changes to database 592 throw convertHibernateAccessException(ex); 593 } 594 } 595 596 @Override 597 protected void doRollback(DefaultTransactionStatus status) { 598 HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); 599 if (status.isDebug()) { 600 logger.debug("Rolling back Hibernate transaction on Session [" + 601 txObject.getSessionHolder().getSession() + "]"); 602 } 603 try { 604 txObject.getSessionHolder().getTransaction().rollback(); 605 } 606 catch (org.hibernate.TransactionException ex) { 607 throw new TransactionSystemException("Could not roll back Hibernate transaction", ex); 608 } 609 catch (HibernateException ex) { 610 // Shouldn't really happen, as a rollback doesn't cause a flush. 611 throw convertHibernateAccessException(ex); 612 } 613 finally { 614 if (!txObject.isNewSession() && !this.hibernateManagedSession) { 615 // Clear all pending inserts/updates/deletes in the Session. 616 // Necessary for pre-bound Sessions, to avoid inconsistent state. 617 txObject.getSessionHolder().getSession().clear(); 618 } 619 } 620 } 621 622 @Override 623 protected void doSetRollbackOnly(DefaultTransactionStatus status) { 624 HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); 625 if (status.isDebug()) { 626 logger.debug("Setting Hibernate transaction on Session [" + 627 txObject.getSessionHolder().getSession() + "] rollback-only"); 628 } 629 txObject.setRollbackOnly(); 630 } 631 632 @Override 633 protected void doCleanupAfterCompletion(Object transaction) { 634 HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; 635 636 // Remove the session holder from the thread. 637 if (txObject.isNewSessionHolder()) { 638 TransactionSynchronizationManager.unbindResource(getSessionFactory()); 639 } 640 641 // Remove the JDBC connection holder from the thread, if exposed. 642 if (getDataSource() != null) { 643 TransactionSynchronizationManager.unbindResource(getDataSource()); 644 } 645 646 Session session = txObject.getSessionHolder().getSession(); 647 if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) { 648 // We're running with connection release mode "on_close": We're able to reset 649 // the isolation level and/or read-only flag of the JDBC Connection here. 650 // Else, we need to rely on the connection pool to perform proper cleanup. 651 try { 652 Connection con = ((SessionImplementor) session).connection(); 653 Integer previousHoldability = txObject.getPreviousHoldability(); 654 if (previousHoldability != null) { 655 con.setHoldability(previousHoldability); 656 } 657 DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel()); 658 } 659 catch (HibernateException ex) { 660 logger.debug("Could not access JDBC Connection of Hibernate Session", ex); 661 } 662 catch (Throwable ex) { 663 logger.debug("Could not reset JDBC Connection after transaction", ex); 664 } 665 } 666 667 if (txObject.isNewSession()) { 668 if (logger.isDebugEnabled()) { 669 logger.debug("Closing Hibernate Session [" + session + "] after transaction"); 670 } 671 SessionFactoryUtils.closeSession(session); 672 } 673 else { 674 if (logger.isDebugEnabled()) { 675 logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction"); 676 } 677 if (txObject.getSessionHolder().getPreviousFlushMode() != null) { 678 session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode()); 679 } 680 if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) { 681 disconnectOnCompletion(session); 682 } 683 } 684 txObject.getSessionHolder().clear(); 685 } 686 687 /** 688 * Disconnect a pre-existing Hibernate Session on transaction completion, 689 * returning its database connection but preserving its entity state. 690 * <p>The default implementation simply calls {@link Session#disconnect()}. 691 * Subclasses may override this with a no-op or with fine-tuned disconnection logic. 692 * @param session the Hibernate Session to disconnect 693 * @since 4.1.2 694 * @see org.hibernate.Session#disconnect() 695 */ 696 protected void disconnectOnCompletion(Session session) { 697 session.disconnect(); 698 } 699 700 /** 701 * Return whether the given Hibernate Session will always hold the same 702 * JDBC Connection. This is used to check whether the transaction manager 703 * can safely prepare and clean up the JDBC Connection used for a transaction. 704 * <p>The default implementation checks the Session's connection release mode 705 * to be "on_close". 706 * @param session the Hibernate Session to check 707 * @see org.hibernate.engine.transaction.spi.TransactionContext#getConnectionReleaseMode() 708 * @see org.hibernate.ConnectionReleaseMode#ON_CLOSE 709 */ 710 protected boolean isSameConnectionForEntireSession(Session session) { 711 if (!(session instanceof TransactionContext)) { 712 // The best we can do is to assume we're safe. 713 return true; 714 } 715 ConnectionReleaseMode releaseMode = ((TransactionContext) session).getConnectionReleaseMode(); 716 return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode); 717 } 718 719 720 /** 721 * Convert the given HibernateException to an appropriate exception 722 * from the {@code org.springframework.dao} hierarchy. 723 * <p>Will automatically apply a specified SQLExceptionTranslator to a 724 * Hibernate JDBCException, else rely on Hibernate's default translation. 725 * @param ex HibernateException that occurred 726 * @return a corresponding DataAccessException 727 * @see SessionFactoryUtils#convertHibernateAccessException 728 */ 729 protected DataAccessException convertHibernateAccessException(HibernateException ex) { 730 return SessionFactoryUtils.convertHibernateAccessException(ex); 731 } 732 733 734 /** 735 * Hibernate transaction object, representing a SessionHolder. 736 * Used as transaction object by HibernateTransactionManager. 737 */ 738 private class HibernateTransactionObject extends JdbcTransactionObjectSupport { 739 740 private SessionHolder sessionHolder; 741 742 private boolean newSessionHolder; 743 744 private boolean newSession; 745 746 private Integer previousHoldability; 747 748 public void setSession(Session session) { 749 this.sessionHolder = new SessionHolder(session); 750 this.newSessionHolder = true; 751 this.newSession = true; 752 } 753 754 public void setExistingSession(Session session) { 755 this.sessionHolder = new SessionHolder(session); 756 this.newSessionHolder = true; 757 this.newSession = false; 758 } 759 760 public void setSessionHolder(SessionHolder sessionHolder) { 761 this.sessionHolder = sessionHolder; 762 this.newSessionHolder = false; 763 this.newSession = false; 764 } 765 766 public SessionHolder getSessionHolder() { 767 return this.sessionHolder; 768 } 769 770 public boolean isNewSessionHolder() { 771 return this.newSessionHolder; 772 } 773 774 public boolean isNewSession() { 775 return this.newSession; 776 } 777 778 public void setPreviousHoldability(Integer previousHoldability) { 779 this.previousHoldability = previousHoldability; 780 } 781 782 public Integer getPreviousHoldability() { 783 return this.previousHoldability; 784 } 785 786 public boolean hasSpringManagedTransaction() { 787 return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null); 788 } 789 790 public boolean hasHibernateManagedTransaction() { 791 return (this.sessionHolder != null && this.sessionHolder.getSession().getTransaction().isActive()); 792 } 793 794 public void setRollbackOnly() { 795 this.sessionHolder.setRollbackOnly(); 796 if (hasConnectionHolder()) { 797 getConnectionHolder().setRollbackOnly(); 798 } 799 } 800 801 @Override 802 public boolean isRollbackOnly() { 803 return this.sessionHolder.isRollbackOnly() || 804 (hasConnectionHolder() && getConnectionHolder().isRollbackOnly()); 805 } 806 807 @Override 808 public void flush() { 809 try { 810 this.sessionHolder.getSession().flush(); 811 } 812 catch (HibernateException ex) { 813 throw convertHibernateAccessException(ex); 814 } 815 } 816 } 817 818 819 /** 820 * Holder for suspended resources. 821 * Used internally by {@code doSuspend} and {@code doResume}. 822 */ 823 private static class SuspendedResourcesHolder { 824 825 private final SessionHolder sessionHolder; 826 827 private final ConnectionHolder connectionHolder; 828 829 private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) { 830 this.sessionHolder = sessionHolder; 831 this.connectionHolder = conHolder; 832 } 833 834 private SessionHolder getSessionHolder() { 835 return this.sessionHolder; 836 } 837 838 private ConnectionHolder getConnectionHolder() { 839 return this.connectionHolder; 840 } 841 } 842 843}