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