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