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