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.transaction.support; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.Serializable; 022import java.util.List; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.core.Constants; 028import org.springframework.transaction.IllegalTransactionStateException; 029import org.springframework.transaction.InvalidTimeoutException; 030import org.springframework.transaction.NestedTransactionNotSupportedException; 031import org.springframework.transaction.PlatformTransactionManager; 032import org.springframework.transaction.TransactionDefinition; 033import org.springframework.transaction.TransactionException; 034import org.springframework.transaction.TransactionStatus; 035import org.springframework.transaction.TransactionSuspensionNotSupportedException; 036import org.springframework.transaction.UnexpectedRollbackException; 037 038/** 039 * Abstract base class that implements Spring's standard transaction workflow, 040 * serving as basis for concrete platform transaction managers like 041 * {@link org.springframework.transaction.jta.JtaTransactionManager}. 042 * 043 * <p>This base class provides the following workflow handling: 044 * <ul> 045 * <li>determines if there is an existing transaction; 046 * <li>applies the appropriate propagation behavior; 047 * <li>suspends and resumes transactions if necessary; 048 * <li>checks the rollback-only flag on commit; 049 * <li>applies the appropriate modification on rollback 050 * (actual rollback or setting rollback-only); 051 * <li>triggers registered synchronization callbacks 052 * (if transaction synchronization is active). 053 * </ul> 054 * 055 * <p>Subclasses have to implement specific template methods for specific 056 * states of a transaction, e.g.: begin, suspend, resume, commit, rollback. 057 * The most important of them are abstract and must be provided by a concrete 058 * implementation; for the rest, defaults are provided, so overriding is optional. 059 * 060 * <p>Transaction synchronization is a generic mechanism for registering callbacks 061 * that get invoked at transaction completion time. This is mainly used internally 062 * by the data access support classes for JDBC, Hibernate, JPA, etc when running 063 * within a JTA transaction: They register resources that are opened within the 064 * transaction for closing at transaction completion time, allowing e.g. for reuse 065 * of the same Hibernate Session within the transaction. The same mechanism can 066 * also be leveraged for custom synchronization needs in an application. 067 * 068 * <p>The state of this class is serializable, to allow for serializing the 069 * transaction strategy along with proxies that carry a transaction interceptor. 070 * It is up to subclasses if they wish to make their state to be serializable too. 071 * They should implement the {@code java.io.Serializable} marker interface in 072 * that case, and potentially a private {@code readObject()} method (according 073 * to Java serialization rules) if they need to restore any transient state. 074 * 075 * @author Juergen Hoeller 076 * @since 28.03.2003 077 * @see #setTransactionSynchronization 078 * @see TransactionSynchronizationManager 079 * @see org.springframework.transaction.jta.JtaTransactionManager 080 */ 081@SuppressWarnings("serial") 082public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { 083 084 /** 085 * Always activate transaction synchronization, even for "empty" transactions 086 * that result from PROPAGATION_SUPPORTS with no existing backend transaction. 087 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS 088 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED 089 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER 090 */ 091 public static final int SYNCHRONIZATION_ALWAYS = 0; 092 093 /** 094 * Activate transaction synchronization only for actual transactions, 095 * that is, not for empty ones that result from PROPAGATION_SUPPORTS with 096 * no existing backend transaction. 097 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED 098 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY 099 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW 100 */ 101 public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1; 102 103 /** 104 * Never active transaction synchronization, not even for actual transactions. 105 */ 106 public static final int SYNCHRONIZATION_NEVER = 2; 107 108 109 /** Constants instance for AbstractPlatformTransactionManager */ 110 private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class); 111 112 113 protected transient Log logger = LogFactory.getLog(getClass()); 114 115 private int transactionSynchronization = SYNCHRONIZATION_ALWAYS; 116 117 private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT; 118 119 private boolean nestedTransactionAllowed = false; 120 121 private boolean validateExistingTransaction = false; 122 123 private boolean globalRollbackOnParticipationFailure = true; 124 125 private boolean failEarlyOnGlobalRollbackOnly = false; 126 127 private boolean rollbackOnCommitFailure = false; 128 129 130 /** 131 * Set the transaction synchronization by the name of the corresponding constant 132 * in this class, e.g. "SYNCHRONIZATION_ALWAYS". 133 * @param constantName name of the constant 134 * @see #SYNCHRONIZATION_ALWAYS 135 */ 136 public final void setTransactionSynchronizationName(String constantName) { 137 setTransactionSynchronization(constants.asNumber(constantName).intValue()); 138 } 139 140 /** 141 * Set when this transaction manager should activate the thread-bound 142 * transaction synchronization support. Default is "always". 143 * <p>Note that transaction synchronization isn't supported for 144 * multiple concurrent transactions by different transaction managers. 145 * Only one transaction manager is allowed to activate it at any time. 146 * @see #SYNCHRONIZATION_ALWAYS 147 * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION 148 * @see #SYNCHRONIZATION_NEVER 149 * @see TransactionSynchronizationManager 150 * @see TransactionSynchronization 151 */ 152 public final void setTransactionSynchronization(int transactionSynchronization) { 153 this.transactionSynchronization = transactionSynchronization; 154 } 155 156 /** 157 * Return if this transaction manager should activate the thread-bound 158 * transaction synchronization support. 159 */ 160 public final int getTransactionSynchronization() { 161 return this.transactionSynchronization; 162 } 163 164 /** 165 * Specify the default timeout that this transaction manager should apply 166 * if there is no timeout specified at the transaction level, in seconds. 167 * <p>Default is the underlying transaction infrastructure's default timeout, 168 * e.g. typically 30 seconds in case of a JTA provider, indicated by the 169 * {@code TransactionDefinition.TIMEOUT_DEFAULT} value. 170 * @see org.springframework.transaction.TransactionDefinition#TIMEOUT_DEFAULT 171 */ 172 public final void setDefaultTimeout(int defaultTimeout) { 173 if (defaultTimeout < TransactionDefinition.TIMEOUT_DEFAULT) { 174 throw new InvalidTimeoutException("Invalid default timeout", defaultTimeout); 175 } 176 this.defaultTimeout = defaultTimeout; 177 } 178 179 /** 180 * Return the default timeout that this transaction manager should apply 181 * if there is no timeout specified at the transaction level, in seconds. 182 * <p>Returns {@code TransactionDefinition.TIMEOUT_DEFAULT} to indicate 183 * the underlying transaction infrastructure's default timeout. 184 */ 185 public final int getDefaultTimeout() { 186 return this.defaultTimeout; 187 } 188 189 /** 190 * Set whether nested transactions are allowed. Default is "false". 191 * <p>Typically initialized with an appropriate default by the 192 * concrete transaction manager subclass. 193 */ 194 public final void setNestedTransactionAllowed(boolean nestedTransactionAllowed) { 195 this.nestedTransactionAllowed = nestedTransactionAllowed; 196 } 197 198 /** 199 * Return whether nested transactions are allowed. 200 */ 201 public final boolean isNestedTransactionAllowed() { 202 return this.nestedTransactionAllowed; 203 } 204 205 /** 206 * Set whether existing transactions should be validated before participating 207 * in them. 208 * <p>When participating in an existing transaction (e.g. with 209 * PROPAGATION_REQUIRED or PROPAGATION_SUPPORTS encountering an existing 210 * transaction), this outer transaction's characteristics will apply even 211 * to the inner transaction scope. Validation will detect incompatible 212 * isolation level and read-only settings on the inner transaction definition 213 * and reject participation accordingly through throwing a corresponding exception. 214 * <p>Default is "false", leniently ignoring inner transaction settings, 215 * simply overriding them with the outer transaction's characteristics. 216 * Switch this flag to "true" in order to enforce strict validation. 217 * @since 2.5.1 218 */ 219 public final void setValidateExistingTransaction(boolean validateExistingTransaction) { 220 this.validateExistingTransaction = validateExistingTransaction; 221 } 222 223 /** 224 * Return whether existing transactions should be validated before participating 225 * in them. 226 * @since 2.5.1 227 */ 228 public final boolean isValidateExistingTransaction() { 229 return this.validateExistingTransaction; 230 } 231 232 /** 233 * Set whether to globally mark an existing transaction as rollback-only 234 * after a participating transaction failed. 235 * <p>Default is "true": If a participating transaction (e.g. with 236 * PROPAGATION_REQUIRED or PROPAGATION_SUPPORTS encountering an existing 237 * transaction) fails, the transaction will be globally marked as rollback-only. 238 * The only possible outcome of such a transaction is a rollback: The 239 * transaction originator <i>cannot</i> make the transaction commit anymore. 240 * <p>Switch this to "false" to let the transaction originator make the rollback 241 * decision. If a participating transaction fails with an exception, the caller 242 * can still decide to continue with a different path within the transaction. 243 * However, note that this will only work as long as all participating resources 244 * are capable of continuing towards a transaction commit even after a data access 245 * failure: This is generally not the case for a Hibernate Session, for example; 246 * neither is it for a sequence of JDBC insert/update/delete operations. 247 * <p><b>Note:</b>This flag only applies to an explicit rollback attempt for a 248 * subtransaction, typically caused by an exception thrown by a data access operation 249 * (where TransactionInterceptor will trigger a {@code PlatformTransactionManager.rollback()} 250 * call according to a rollback rule). If the flag is off, the caller can handle the exception 251 * and decide on a rollback, independent of the rollback rules of the subtransaction. 252 * This flag does, however, <i>not</i> apply to explicit {@code setRollbackOnly} 253 * calls on a {@code TransactionStatus}, which will always cause an eventual 254 * global rollback (as it might not throw an exception after the rollback-only call). 255 * <p>The recommended solution for handling failure of a subtransaction 256 * is a "nested transaction", where the global transaction can be rolled 257 * back to a savepoint taken at the beginning of the subtransaction. 258 * PROPAGATION_NESTED provides exactly those semantics; however, it will 259 * only work when nested transaction support is available. This is the case 260 * with DataSourceTransactionManager, but not with JtaTransactionManager. 261 * @see #setNestedTransactionAllowed 262 * @see org.springframework.transaction.jta.JtaTransactionManager 263 */ 264 public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) { 265 this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure; 266 } 267 268 /** 269 * Return whether to globally mark an existing transaction as rollback-only 270 * after a participating transaction failed. 271 */ 272 public final boolean isGlobalRollbackOnParticipationFailure() { 273 return this.globalRollbackOnParticipationFailure; 274 } 275 276 /** 277 * Set whether to fail early in case of the transaction being globally marked 278 * as rollback-only. 279 * <p>Default is "false", only causing an UnexpectedRollbackException at the 280 * outermost transaction boundary. Switch this flag on to cause an 281 * UnexpectedRollbackException as early as the global rollback-only marker 282 * has been first detected, even from within an inner transaction boundary. 283 * <p>Note that, as of Spring 2.0, the fail-early behavior for global 284 * rollback-only markers has been unified: All transaction managers will by 285 * default only cause UnexpectedRollbackException at the outermost transaction 286 * boundary. This allows, for example, to continue unit tests even after an 287 * operation failed and the transaction will never be completed. All transaction 288 * managers will only fail earlier if this flag has explicitly been set to "true". 289 * @since 2.0 290 * @see org.springframework.transaction.UnexpectedRollbackException 291 */ 292 public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) { 293 this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly; 294 } 295 296 /** 297 * Return whether to fail early in case of the transaction being globally marked 298 * as rollback-only. 299 * @since 2.0 300 */ 301 public final boolean isFailEarlyOnGlobalRollbackOnly() { 302 return this.failEarlyOnGlobalRollbackOnly; 303 } 304 305 /** 306 * Set whether {@code doRollback} should be performed on failure of the 307 * {@code doCommit} call. Typically not necessary and thus to be avoided, 308 * as it can potentially override the commit exception with a subsequent 309 * rollback exception. 310 * <p>Default is "false". 311 * @see #doCommit 312 * @see #doRollback 313 */ 314 public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) { 315 this.rollbackOnCommitFailure = rollbackOnCommitFailure; 316 } 317 318 /** 319 * Return whether {@code doRollback} should be performed on failure of the 320 * {@code doCommit} call. 321 */ 322 public final boolean isRollbackOnCommitFailure() { 323 return this.rollbackOnCommitFailure; 324 } 325 326 327 //--------------------------------------------------------------------- 328 // Implementation of PlatformTransactionManager 329 //--------------------------------------------------------------------- 330 331 /** 332 * This implementation handles propagation behavior. Delegates to 333 * {@code doGetTransaction}, {@code isExistingTransaction} 334 * and {@code doBegin}. 335 * @see #doGetTransaction 336 * @see #isExistingTransaction 337 * @see #doBegin 338 */ 339 @Override 340 public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { 341 Object transaction = doGetTransaction(); 342 343 // Cache debug flag to avoid repeated checks. 344 boolean debugEnabled = logger.isDebugEnabled(); 345 346 if (definition == null) { 347 // Use defaults if no transaction definition given. 348 definition = new DefaultTransactionDefinition(); 349 } 350 351 if (isExistingTransaction(transaction)) { 352 // Existing transaction found -> check propagation behavior to find out how to behave. 353 return handleExistingTransaction(definition, transaction, debugEnabled); 354 } 355 356 // Check definition settings for new transaction. 357 if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { 358 throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); 359 } 360 361 // No existing transaction found -> check propagation behavior to find out how to proceed. 362 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { 363 throw new IllegalTransactionStateException( 364 "No existing transaction found for transaction marked with propagation 'mandatory'"); 365 } 366 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || 367 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || 368 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { 369 SuspendedResourcesHolder suspendedResources = suspend(null); 370 if (debugEnabled) { 371 logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); 372 } 373 try { 374 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 375 DefaultTransactionStatus status = newTransactionStatus( 376 definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); 377 doBegin(transaction, definition); 378 prepareSynchronization(status, definition); 379 return status; 380 } 381 catch (RuntimeException ex) { 382 resume(null, suspendedResources); 383 throw ex; 384 } 385 catch (Error err) { 386 resume(null, suspendedResources); 387 throw err; 388 } 389 } 390 else { 391 // Create "empty" transaction: no actual transaction, but potentially synchronization. 392 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) { 393 logger.warn("Custom isolation level specified but no actual transaction initiated; " + 394 "isolation level will effectively be ignored: " + definition); 395 } 396 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); 397 return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); 398 } 399 } 400 401 /** 402 * Create a TransactionStatus for an existing transaction. 403 */ 404 private TransactionStatus handleExistingTransaction( 405 TransactionDefinition definition, Object transaction, boolean debugEnabled) 406 throws TransactionException { 407 408 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { 409 throw new IllegalTransactionStateException( 410 "Existing transaction found for transaction marked with propagation 'never'"); 411 } 412 413 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { 414 if (debugEnabled) { 415 logger.debug("Suspending current transaction"); 416 } 417 Object suspendedResources = suspend(transaction); 418 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); 419 return prepareTransactionStatus( 420 definition, null, false, newSynchronization, debugEnabled, suspendedResources); 421 } 422 423 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { 424 if (debugEnabled) { 425 logger.debug("Suspending current transaction, creating new transaction with name [" + 426 definition.getName() + "]"); 427 } 428 SuspendedResourcesHolder suspendedResources = suspend(transaction); 429 try { 430 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 431 DefaultTransactionStatus status = newTransactionStatus( 432 definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); 433 doBegin(transaction, definition); 434 prepareSynchronization(status, definition); 435 return status; 436 } 437 catch (RuntimeException beginEx) { 438 resumeAfterBeginException(transaction, suspendedResources, beginEx); 439 throw beginEx; 440 } 441 catch (Error beginErr) { 442 resumeAfterBeginException(transaction, suspendedResources, beginErr); 443 throw beginErr; 444 } 445 } 446 447 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { 448 if (!isNestedTransactionAllowed()) { 449 throw new NestedTransactionNotSupportedException( 450 "Transaction manager does not allow nested transactions by default - " + 451 "specify 'nestedTransactionAllowed' property with value 'true'"); 452 } 453 if (debugEnabled) { 454 logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); 455 } 456 if (useSavepointForNestedTransaction()) { 457 // Create savepoint within existing Spring-managed transaction, 458 // through the SavepointManager API implemented by TransactionStatus. 459 // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. 460 DefaultTransactionStatus status = 461 prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); 462 status.createAndHoldSavepoint(); 463 return status; 464 } 465 else { 466 // Nested transaction through nested begin and commit/rollback calls. 467 // Usually only for JTA: Spring synchronization might get activated here 468 // in case of a pre-existing JTA transaction. 469 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 470 DefaultTransactionStatus status = newTransactionStatus( 471 definition, transaction, true, newSynchronization, debugEnabled, null); 472 doBegin(transaction, definition); 473 prepareSynchronization(status, definition); 474 return status; 475 } 476 } 477 478 // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED. 479 if (debugEnabled) { 480 logger.debug("Participating in existing transaction"); 481 } 482 if (isValidateExistingTransaction()) { 483 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { 484 Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); 485 if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) { 486 Constants isoConstants = DefaultTransactionDefinition.constants; 487 throw new IllegalTransactionStateException("Participating transaction with definition [" + 488 definition + "] specifies isolation level which is incompatible with existing transaction: " + 489 (currentIsolationLevel != null ? 490 isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : 491 "(unknown)")); 492 } 493 } 494 if (!definition.isReadOnly()) { 495 if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { 496 throw new IllegalTransactionStateException("Participating transaction with definition [" + 497 definition + "] is not marked as read-only but existing transaction is"); 498 } 499 } 500 } 501 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 502 return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); 503 } 504 505 /** 506 * Create a new TransactionStatus for the given arguments, 507 * also initializing transaction synchronization as appropriate. 508 * @see #newTransactionStatus 509 * @see #prepareTransactionStatus 510 */ 511 protected final DefaultTransactionStatus prepareTransactionStatus( 512 TransactionDefinition definition, Object transaction, boolean newTransaction, 513 boolean newSynchronization, boolean debug, Object suspendedResources) { 514 515 DefaultTransactionStatus status = newTransactionStatus( 516 definition, transaction, newTransaction, newSynchronization, debug, suspendedResources); 517 prepareSynchronization(status, definition); 518 return status; 519 } 520 521 /** 522 * Create a TransactionStatus instance for the given arguments. 523 */ 524 protected DefaultTransactionStatus newTransactionStatus( 525 TransactionDefinition definition, Object transaction, boolean newTransaction, 526 boolean newSynchronization, boolean debug, Object suspendedResources) { 527 528 boolean actualNewSynchronization = newSynchronization && 529 !TransactionSynchronizationManager.isSynchronizationActive(); 530 return new DefaultTransactionStatus( 531 transaction, newTransaction, actualNewSynchronization, 532 definition.isReadOnly(), debug, suspendedResources); 533 } 534 535 /** 536 * Initialize transaction synchronization as appropriate. 537 */ 538 protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) { 539 if (status.isNewSynchronization()) { 540 TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction()); 541 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( 542 definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ? 543 definition.getIsolationLevel() : null); 544 TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly()); 545 TransactionSynchronizationManager.setCurrentTransactionName(definition.getName()); 546 TransactionSynchronizationManager.initSynchronization(); 547 } 548 } 549 550 /** 551 * Determine the actual timeout to use for the given definition. 552 * Will fall back to this manager's default timeout if the 553 * transaction definition doesn't specify a non-default value. 554 * @param definition the transaction definition 555 * @return the actual timeout to use 556 * @see org.springframework.transaction.TransactionDefinition#getTimeout() 557 * @see #setDefaultTimeout 558 */ 559 protected int determineTimeout(TransactionDefinition definition) { 560 if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) { 561 return definition.getTimeout(); 562 } 563 return getDefaultTimeout(); 564 } 565 566 567 /** 568 * Suspend the given transaction. Suspends transaction synchronization first, 569 * then delegates to the {@code doSuspend} template method. 570 * @param transaction the current transaction object 571 * (or {@code null} to just suspend active synchronizations, if any) 572 * @return an object that holds suspended resources 573 * (or {@code null} if neither transaction nor synchronization active) 574 * @see #doSuspend 575 * @see #resume 576 */ 577 protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException { 578 if (TransactionSynchronizationManager.isSynchronizationActive()) { 579 List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization(); 580 try { 581 Object suspendedResources = null; 582 if (transaction != null) { 583 suspendedResources = doSuspend(transaction); 584 } 585 String name = TransactionSynchronizationManager.getCurrentTransactionName(); 586 TransactionSynchronizationManager.setCurrentTransactionName(null); 587 boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); 588 TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); 589 Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); 590 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); 591 boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive(); 592 TransactionSynchronizationManager.setActualTransactionActive(false); 593 return new SuspendedResourcesHolder( 594 suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive); 595 } 596 catch (RuntimeException ex) { 597 // doSuspend failed - original transaction is still active... 598 doResumeSynchronization(suspendedSynchronizations); 599 throw ex; 600 } 601 catch (Error err) { 602 // doSuspend failed - original transaction is still active... 603 doResumeSynchronization(suspendedSynchronizations); 604 throw err; 605 } 606 } 607 else if (transaction != null) { 608 // Transaction active but no synchronization active. 609 Object suspendedResources = doSuspend(transaction); 610 return new SuspendedResourcesHolder(suspendedResources); 611 } 612 else { 613 // Neither transaction nor synchronization active. 614 return null; 615 } 616 } 617 618 /** 619 * Resume the given transaction. Delegates to the {@code doResume} 620 * template method first, then resuming transaction synchronization. 621 * @param transaction the current transaction object 622 * @param resourcesHolder the object that holds suspended resources, 623 * as returned by {@code suspend} (or {@code null} to just 624 * resume synchronizations, if any) 625 * @see #doResume 626 * @see #suspend 627 */ 628 protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder) 629 throws TransactionException { 630 631 if (resourcesHolder != null) { 632 Object suspendedResources = resourcesHolder.suspendedResources; 633 if (suspendedResources != null) { 634 doResume(transaction, suspendedResources); 635 } 636 List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations; 637 if (suspendedSynchronizations != null) { 638 TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive); 639 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel); 640 TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly); 641 TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name); 642 doResumeSynchronization(suspendedSynchronizations); 643 } 644 } 645 } 646 647 /** 648 * Resume outer transaction after inner transaction begin failed. 649 */ 650 private void resumeAfterBeginException( 651 Object transaction, SuspendedResourcesHolder suspendedResources, Throwable beginEx) { 652 653 String exMessage = "Inner transaction begin exception overridden by outer transaction resume exception"; 654 try { 655 resume(transaction, suspendedResources); 656 } 657 catch (RuntimeException resumeEx) { 658 logger.error(exMessage, beginEx); 659 throw resumeEx; 660 } 661 catch (Error resumeErr) { 662 logger.error(exMessage, beginEx); 663 throw resumeErr; 664 } 665 } 666 667 /** 668 * Suspend all current synchronizations and deactivate transaction 669 * synchronization for the current thread. 670 * @return the List of suspended TransactionSynchronization objects 671 */ 672 private List<TransactionSynchronization> doSuspendSynchronization() { 673 List<TransactionSynchronization> suspendedSynchronizations = 674 TransactionSynchronizationManager.getSynchronizations(); 675 for (TransactionSynchronization synchronization : suspendedSynchronizations) { 676 synchronization.suspend(); 677 } 678 TransactionSynchronizationManager.clearSynchronization(); 679 return suspendedSynchronizations; 680 } 681 682 /** 683 * Reactivate transaction synchronization for the current thread 684 * and resume all given synchronizations. 685 * @param suspendedSynchronizations a List of TransactionSynchronization objects 686 */ 687 private void doResumeSynchronization(List<TransactionSynchronization> suspendedSynchronizations) { 688 TransactionSynchronizationManager.initSynchronization(); 689 for (TransactionSynchronization synchronization : suspendedSynchronizations) { 690 synchronization.resume(); 691 TransactionSynchronizationManager.registerSynchronization(synchronization); 692 } 693 } 694 695 696 /** 697 * This implementation of commit handles participating in existing 698 * transactions and programmatic rollback requests. 699 * Delegates to {@code isRollbackOnly}, {@code doCommit} 700 * and {@code rollback}. 701 * @see org.springframework.transaction.TransactionStatus#isRollbackOnly() 702 * @see #doCommit 703 * @see #rollback 704 */ 705 @Override 706 public final void commit(TransactionStatus status) throws TransactionException { 707 if (status.isCompleted()) { 708 throw new IllegalTransactionStateException( 709 "Transaction is already completed - do not call commit or rollback more than once per transaction"); 710 } 711 712 DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; 713 if (defStatus.isLocalRollbackOnly()) { 714 if (defStatus.isDebug()) { 715 logger.debug("Transactional code has requested rollback"); 716 } 717 processRollback(defStatus); 718 return; 719 } 720 if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { 721 if (defStatus.isDebug()) { 722 logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); 723 } 724 processRollback(defStatus); 725 // Throw UnexpectedRollbackException only at outermost transaction boundary 726 // or if explicitly asked to. 727 if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { 728 throw new UnexpectedRollbackException( 729 "Transaction rolled back because it has been marked as rollback-only"); 730 } 731 return; 732 } 733 734 processCommit(defStatus); 735 } 736 737 /** 738 * Process an actual commit. 739 * Rollback-only flags have already been checked and applied. 740 * @param status object representing the transaction 741 * @throws TransactionException in case of commit failure 742 */ 743 private void processCommit(DefaultTransactionStatus status) throws TransactionException { 744 try { 745 boolean beforeCompletionInvoked = false; 746 try { 747 prepareForCommit(status); 748 triggerBeforeCommit(status); 749 triggerBeforeCompletion(status); 750 beforeCompletionInvoked = true; 751 boolean globalRollbackOnly = false; 752 if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { 753 globalRollbackOnly = status.isGlobalRollbackOnly(); 754 } 755 if (status.hasSavepoint()) { 756 if (status.isDebug()) { 757 logger.debug("Releasing transaction savepoint"); 758 } 759 status.releaseHeldSavepoint(); 760 } 761 else if (status.isNewTransaction()) { 762 if (status.isDebug()) { 763 logger.debug("Initiating transaction commit"); 764 } 765 doCommit(status); 766 } 767 // Throw UnexpectedRollbackException if we have a global rollback-only 768 // marker but still didn't get a corresponding exception from commit. 769 if (globalRollbackOnly) { 770 throw new UnexpectedRollbackException( 771 "Transaction silently rolled back because it has been marked as rollback-only"); 772 } 773 } 774 catch (UnexpectedRollbackException ex) { 775 // can only be caused by doCommit 776 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 777 throw ex; 778 } 779 catch (TransactionException ex) { 780 // can only be caused by doCommit 781 if (isRollbackOnCommitFailure()) { 782 doRollbackOnCommitException(status, ex); 783 } 784 else { 785 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 786 } 787 throw ex; 788 } 789 catch (RuntimeException ex) { 790 if (!beforeCompletionInvoked) { 791 triggerBeforeCompletion(status); 792 } 793 doRollbackOnCommitException(status, ex); 794 throw ex; 795 } 796 catch (Error err) { 797 if (!beforeCompletionInvoked) { 798 triggerBeforeCompletion(status); 799 } 800 doRollbackOnCommitException(status, err); 801 throw err; 802 } 803 804 // Trigger afterCommit callbacks, with an exception thrown there 805 // propagated to callers but the transaction still considered as committed. 806 try { 807 triggerAfterCommit(status); 808 } 809 finally { 810 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); 811 } 812 813 } 814 finally { 815 cleanupAfterCompletion(status); 816 } 817 } 818 819 /** 820 * This implementation of rollback handles participating in existing 821 * transactions. Delegates to {@code doRollback} and 822 * {@code doSetRollbackOnly}. 823 * @see #doRollback 824 * @see #doSetRollbackOnly 825 */ 826 @Override 827 public final void rollback(TransactionStatus status) throws TransactionException { 828 if (status.isCompleted()) { 829 throw new IllegalTransactionStateException( 830 "Transaction is already completed - do not call commit or rollback more than once per transaction"); 831 } 832 833 DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; 834 processRollback(defStatus); 835 } 836 837 /** 838 * Process an actual rollback. 839 * The completed flag has already been checked. 840 * @param status object representing the transaction 841 * @throws TransactionException in case of rollback failure 842 */ 843 private void processRollback(DefaultTransactionStatus status) { 844 try { 845 try { 846 triggerBeforeCompletion(status); 847 if (status.hasSavepoint()) { 848 if (status.isDebug()) { 849 logger.debug("Rolling back transaction to savepoint"); 850 } 851 status.rollbackToHeldSavepoint(); 852 } 853 else if (status.isNewTransaction()) { 854 if (status.isDebug()) { 855 logger.debug("Initiating transaction rollback"); 856 } 857 doRollback(status); 858 } 859 else if (status.hasTransaction()) { 860 if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { 861 if (status.isDebug()) { 862 logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); 863 } 864 doSetRollbackOnly(status); 865 } 866 else { 867 if (status.isDebug()) { 868 logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); 869 } 870 } 871 } 872 else { 873 logger.debug("Should roll back transaction but cannot - no transaction available"); 874 } 875 } 876 catch (RuntimeException ex) { 877 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 878 throw ex; 879 } 880 catch (Error err) { 881 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 882 throw err; 883 } 884 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 885 } 886 finally { 887 cleanupAfterCompletion(status); 888 } 889 } 890 891 /** 892 * Invoke {@code doRollback}, handling rollback exceptions properly. 893 * @param status object representing the transaction 894 * @param ex the thrown application exception or error 895 * @throws TransactionException in case of rollback failure 896 * @see #doRollback 897 */ 898 private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable ex) throws TransactionException { 899 try { 900 if (status.isNewTransaction()) { 901 if (status.isDebug()) { 902 logger.debug("Initiating transaction rollback after commit exception", ex); 903 } 904 doRollback(status); 905 } 906 else if (status.hasTransaction() && isGlobalRollbackOnParticipationFailure()) { 907 if (status.isDebug()) { 908 logger.debug("Marking existing transaction as rollback-only after commit exception", ex); 909 } 910 doSetRollbackOnly(status); 911 } 912 } 913 catch (RuntimeException rbex) { 914 logger.error("Commit exception overridden by rollback exception", ex); 915 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 916 throw rbex; 917 } 918 catch (Error rberr) { 919 logger.error("Commit exception overridden by rollback exception", ex); 920 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 921 throw rberr; 922 } 923 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 924 } 925 926 927 /** 928 * Trigger {@code beforeCommit} callbacks. 929 * @param status object representing the transaction 930 */ 931 protected final void triggerBeforeCommit(DefaultTransactionStatus status) { 932 if (status.isNewSynchronization()) { 933 if (status.isDebug()) { 934 logger.trace("Triggering beforeCommit synchronization"); 935 } 936 TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly()); 937 } 938 } 939 940 /** 941 * Trigger {@code beforeCompletion} callbacks. 942 * @param status object representing the transaction 943 */ 944 protected final void triggerBeforeCompletion(DefaultTransactionStatus status) { 945 if (status.isNewSynchronization()) { 946 if (status.isDebug()) { 947 logger.trace("Triggering beforeCompletion synchronization"); 948 } 949 TransactionSynchronizationUtils.triggerBeforeCompletion(); 950 } 951 } 952 953 /** 954 * Trigger {@code afterCommit} callbacks. 955 * @param status object representing the transaction 956 */ 957 private void triggerAfterCommit(DefaultTransactionStatus status) { 958 if (status.isNewSynchronization()) { 959 if (status.isDebug()) { 960 logger.trace("Triggering afterCommit synchronization"); 961 } 962 TransactionSynchronizationUtils.triggerAfterCommit(); 963 } 964 } 965 966 /** 967 * Trigger {@code afterCompletion} callbacks. 968 * @param status object representing the transaction 969 * @param completionStatus completion status according to TransactionSynchronization constants 970 */ 971 private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) { 972 if (status.isNewSynchronization()) { 973 List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations(); 974 TransactionSynchronizationManager.clearSynchronization(); 975 if (!status.hasTransaction() || status.isNewTransaction()) { 976 if (status.isDebug()) { 977 logger.trace("Triggering afterCompletion synchronization"); 978 } 979 // No transaction or new transaction for the current scope -> 980 // invoke the afterCompletion callbacks immediately 981 invokeAfterCompletion(synchronizations, completionStatus); 982 } 983 else if (!synchronizations.isEmpty()) { 984 // Existing transaction that we participate in, controlled outside 985 // of the scope of this Spring transaction manager -> try to register 986 // an afterCompletion callback with the existing (JTA) transaction. 987 registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations); 988 } 989 } 990 } 991 992 /** 993 * Actually invoke the {@code afterCompletion} methods of the 994 * given Spring TransactionSynchronization objects. 995 * <p>To be called by this abstract manager itself, or by special implementations 996 * of the {@code registerAfterCompletionWithExistingTransaction} callback. 997 * @param synchronizations a List of TransactionSynchronization objects 998 * @param completionStatus the completion status according to the 999 * constants in the TransactionSynchronization interface 1000 * @see #registerAfterCompletionWithExistingTransaction(Object, java.util.List) 1001 * @see TransactionSynchronization#STATUS_COMMITTED 1002 * @see TransactionSynchronization#STATUS_ROLLED_BACK 1003 * @see TransactionSynchronization#STATUS_UNKNOWN 1004 */ 1005 protected final void invokeAfterCompletion(List<TransactionSynchronization> synchronizations, int completionStatus) { 1006 TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus); 1007 } 1008 1009 /** 1010 * Clean up after completion, clearing synchronization if necessary, 1011 * and invoking doCleanupAfterCompletion. 1012 * @param status object representing the transaction 1013 * @see #doCleanupAfterCompletion 1014 */ 1015 private void cleanupAfterCompletion(DefaultTransactionStatus status) { 1016 status.setCompleted(); 1017 if (status.isNewSynchronization()) { 1018 TransactionSynchronizationManager.clear(); 1019 } 1020 if (status.isNewTransaction()) { 1021 doCleanupAfterCompletion(status.getTransaction()); 1022 } 1023 if (status.getSuspendedResources() != null) { 1024 if (status.isDebug()) { 1025 logger.debug("Resuming suspended transaction after completion of inner transaction"); 1026 } 1027 resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources()); 1028 } 1029 } 1030 1031 1032 //--------------------------------------------------------------------- 1033 // Template methods to be implemented in subclasses 1034 //--------------------------------------------------------------------- 1035 1036 /** 1037 * Return a transaction object for the current transaction state. 1038 * <p>The returned object will usually be specific to the concrete transaction 1039 * manager implementation, carrying corresponding transaction state in a 1040 * modifiable fashion. This object will be passed into the other template 1041 * methods (e.g. doBegin and doCommit), either directly or as part of a 1042 * DefaultTransactionStatus instance. 1043 * <p>The returned object should contain information about any existing 1044 * transaction, that is, a transaction that has already started before the 1045 * current {@code getTransaction} call on the transaction manager. 1046 * Consequently, a {@code doGetTransaction} implementation will usually 1047 * look for an existing transaction and store corresponding state in the 1048 * returned transaction object. 1049 * @return the current transaction object 1050 * @throws org.springframework.transaction.CannotCreateTransactionException 1051 * if transaction support is not available 1052 * @throws TransactionException in case of lookup or system errors 1053 * @see #doBegin 1054 * @see #doCommit 1055 * @see #doRollback 1056 * @see DefaultTransactionStatus#getTransaction 1057 */ 1058 protected abstract Object doGetTransaction() throws TransactionException; 1059 1060 /** 1061 * Check if the given transaction object indicates an existing transaction 1062 * (that is, a transaction which has already started). 1063 * <p>The result will be evaluated according to the specified propagation 1064 * behavior for the new transaction. An existing transaction might get 1065 * suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction 1066 * might participate in the existing one (in case of PROPAGATION_REQUIRED). 1067 * <p>The default implementation returns {@code false}, assuming that 1068 * participating in existing transactions is generally not supported. 1069 * Subclasses are of course encouraged to provide such support. 1070 * @param transaction transaction object returned by doGetTransaction 1071 * @return if there is an existing transaction 1072 * @throws TransactionException in case of system errors 1073 * @see #doGetTransaction 1074 */ 1075 protected boolean isExistingTransaction(Object transaction) throws TransactionException { 1076 return false; 1077 } 1078 1079 /** 1080 * Return whether to use a savepoint for a nested transaction. 1081 * <p>Default is {@code true}, which causes delegation to DefaultTransactionStatus 1082 * for creating and holding a savepoint. If the transaction object does not implement 1083 * the SavepointManager interface, a NestedTransactionNotSupportedException will be 1084 * thrown. Else, the SavepointManager will be asked to create a new savepoint to 1085 * demarcate the start of the nested transaction. 1086 * <p>Subclasses can override this to return {@code false}, causing a further 1087 * call to {@code doBegin} - within the context of an already existing transaction. 1088 * The {@code doBegin} implementation needs to handle this accordingly in such 1089 * a scenario. This is appropriate for JTA, for example. 1090 * @see DefaultTransactionStatus#createAndHoldSavepoint 1091 * @see DefaultTransactionStatus#rollbackToHeldSavepoint 1092 * @see DefaultTransactionStatus#releaseHeldSavepoint 1093 * @see #doBegin 1094 */ 1095 protected boolean useSavepointForNestedTransaction() { 1096 return true; 1097 } 1098 1099 /** 1100 * Begin a new transaction with semantics according to the given transaction 1101 * definition. Does not have to care about applying the propagation behavior, 1102 * as this has already been handled by this abstract manager. 1103 * <p>This method gets called when the transaction manager has decided to actually 1104 * start a new transaction. Either there wasn't any transaction before, or the 1105 * previous transaction has been suspended. 1106 * <p>A special scenario is a nested transaction without savepoint: If 1107 * {@code useSavepointForNestedTransaction()} returns "false", this method 1108 * will be called to start a nested transaction when necessary. In such a context, 1109 * there will be an active transaction: The implementation of this method has 1110 * to detect this and start an appropriate nested transaction. 1111 * @param transaction transaction object returned by {@code doGetTransaction} 1112 * @param definition TransactionDefinition instance, describing propagation 1113 * behavior, isolation level, read-only flag, timeout, and transaction name 1114 * @throws TransactionException in case of creation or system errors 1115 */ 1116 protected abstract void doBegin(Object transaction, TransactionDefinition definition) 1117 throws TransactionException; 1118 1119 /** 1120 * Suspend the resources of the current transaction. 1121 * Transaction synchronization will already have been suspended. 1122 * <p>The default implementation throws a TransactionSuspensionNotSupportedException, 1123 * assuming that transaction suspension is generally not supported. 1124 * @param transaction transaction object returned by {@code doGetTransaction} 1125 * @return an object that holds suspended resources 1126 * (will be kept unexamined for passing it into doResume) 1127 * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException 1128 * if suspending is not supported by the transaction manager implementation 1129 * @throws TransactionException in case of system errors 1130 * @see #doResume 1131 */ 1132 protected Object doSuspend(Object transaction) throws TransactionException { 1133 throw new TransactionSuspensionNotSupportedException( 1134 "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); 1135 } 1136 1137 /** 1138 * Resume the resources of the current transaction. 1139 * Transaction synchronization will be resumed afterwards. 1140 * <p>The default implementation throws a TransactionSuspensionNotSupportedException, 1141 * assuming that transaction suspension is generally not supported. 1142 * @param transaction transaction object returned by {@code doGetTransaction} 1143 * @param suspendedResources the object that holds suspended resources, 1144 * as returned by doSuspend 1145 * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException 1146 * if resuming is not supported by the transaction manager implementation 1147 * @throws TransactionException in case of system errors 1148 * @see #doSuspend 1149 */ 1150 protected void doResume(Object transaction, Object suspendedResources) throws TransactionException { 1151 throw new TransactionSuspensionNotSupportedException( 1152 "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); 1153 } 1154 1155 /** 1156 * Return whether to call {@code doCommit} on a transaction that has been 1157 * marked as rollback-only in a global fashion. 1158 * <p>Does not apply if an application locally sets the transaction to rollback-only 1159 * via the TransactionStatus, but only to the transaction itself being marked as 1160 * rollback-only by the transaction coordinator. 1161 * <p>Default is "false": Local transaction strategies usually don't hold the rollback-only 1162 * marker in the transaction itself, therefore they can't handle rollback-only transactions 1163 * as part of transaction commit. Hence, AbstractPlatformTransactionManager will trigger 1164 * a rollback in that case, throwing an UnexpectedRollbackException afterwards. 1165 * <p>Override this to return "true" if the concrete transaction manager expects a 1166 * {@code doCommit} call even for a rollback-only transaction, allowing for 1167 * special handling there. This will, for example, be the case for JTA, where 1168 * {@code UserTransaction.commit} will check the read-only flag itself and 1169 * throw a corresponding RollbackException, which might include the specific reason 1170 * (such as a transaction timeout). 1171 * <p>If this method returns "true" but the {@code doCommit} implementation does not 1172 * throw an exception, this transaction manager will throw an UnexpectedRollbackException 1173 * itself. This should not be the typical case; it is mainly checked to cover misbehaving 1174 * JTA providers that silently roll back even when the rollback has not been requested 1175 * by the calling code. 1176 * @see #doCommit 1177 * @see DefaultTransactionStatus#isGlobalRollbackOnly() 1178 * @see DefaultTransactionStatus#isLocalRollbackOnly() 1179 * @see org.springframework.transaction.TransactionStatus#setRollbackOnly() 1180 * @see org.springframework.transaction.UnexpectedRollbackException 1181 * @see javax.transaction.UserTransaction#commit() 1182 * @see javax.transaction.RollbackException 1183 */ 1184 protected boolean shouldCommitOnGlobalRollbackOnly() { 1185 return false; 1186 } 1187 1188 /** 1189 * Make preparations for commit, to be performed before the 1190 * {@code beforeCommit} synchronization callbacks occur. 1191 * <p>Note that exceptions will get propagated to the commit caller 1192 * and cause a rollback of the transaction. 1193 * @param status the status representation of the transaction 1194 * @throws RuntimeException in case of errors; will be <b>propagated to the caller</b> 1195 * (note: do not throw TransactionException subclasses here!) 1196 */ 1197 protected void prepareForCommit(DefaultTransactionStatus status) { 1198 } 1199 1200 /** 1201 * Perform an actual commit of the given transaction. 1202 * <p>An implementation does not need to check the "new transaction" flag 1203 * or the rollback-only flag; this will already have been handled before. 1204 * Usually, a straight commit will be performed on the transaction object 1205 * contained in the passed-in status. 1206 * @param status the status representation of the transaction 1207 * @throws TransactionException in case of commit or system errors 1208 * @see DefaultTransactionStatus#getTransaction 1209 */ 1210 protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException; 1211 1212 /** 1213 * Perform an actual rollback of the given transaction. 1214 * <p>An implementation does not need to check the "new transaction" flag; 1215 * this will already have been handled before. Usually, a straight rollback 1216 * will be performed on the transaction object contained in the passed-in status. 1217 * @param status the status representation of the transaction 1218 * @throws TransactionException in case of system errors 1219 * @see DefaultTransactionStatus#getTransaction 1220 */ 1221 protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException; 1222 1223 /** 1224 * Set the given transaction rollback-only. Only called on rollback 1225 * if the current transaction participates in an existing one. 1226 * <p>The default implementation throws an IllegalTransactionStateException, 1227 * assuming that participating in existing transactions is generally not 1228 * supported. Subclasses are of course encouraged to provide such support. 1229 * @param status the status representation of the transaction 1230 * @throws TransactionException in case of system errors 1231 */ 1232 protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException { 1233 throw new IllegalTransactionStateException( 1234 "Participating in existing transactions is not supported - when 'isExistingTransaction' " + 1235 "returns true, appropriate 'doSetRollbackOnly' behavior must be provided"); 1236 } 1237 1238 /** 1239 * Register the given list of transaction synchronizations with the existing transaction. 1240 * <p>Invoked when the control of the Spring transaction manager and thus all Spring 1241 * transaction synchronizations end, without the transaction being completed yet. This 1242 * is for example the case when participating in an existing JTA or EJB CMT transaction. 1243 * <p>The default implementation simply invokes the {@code afterCompletion} methods 1244 * immediately, passing in "STATUS_UNKNOWN". This is the best we can do if there's no 1245 * chance to determine the actual outcome of the outer transaction. 1246 * @param transaction transaction object returned by {@code doGetTransaction} 1247 * @param synchronizations a List of TransactionSynchronization objects 1248 * @throws TransactionException in case of system errors 1249 * @see #invokeAfterCompletion(java.util.List, int) 1250 * @see TransactionSynchronization#afterCompletion(int) 1251 * @see TransactionSynchronization#STATUS_UNKNOWN 1252 */ 1253 protected void registerAfterCompletionWithExistingTransaction( 1254 Object transaction, List<TransactionSynchronization> synchronizations) throws TransactionException { 1255 1256 logger.debug("Cannot register Spring after-completion synchronization with existing transaction - " + 1257 "processing Spring after-completion callbacks immediately, with outcome status 'unknown'"); 1258 invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); 1259 } 1260 1261 /** 1262 * Cleanup resources after transaction completion. 1263 * <p>Called after {@code doCommit} and {@code doRollback} execution, 1264 * on any outcome. The default implementation does nothing. 1265 * <p>Should not throw any exceptions but just issue warnings on errors. 1266 * @param transaction transaction object returned by {@code doGetTransaction} 1267 */ 1268 protected void doCleanupAfterCompletion(Object transaction) { 1269 } 1270 1271 1272 //--------------------------------------------------------------------- 1273 // Serialization support 1274 //--------------------------------------------------------------------- 1275 1276 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 1277 // Rely on default serialization; just initialize state after deserialization. 1278 ois.defaultReadObject(); 1279 1280 // Initialize transient fields. 1281 this.logger = LogFactory.getLog(getClass()); 1282 } 1283 1284 1285 /** 1286 * Holder for suspended resources. 1287 * Used internally by {@code suspend} and {@code resume}. 1288 */ 1289 protected static class SuspendedResourcesHolder { 1290 1291 private final Object suspendedResources; 1292 1293 private List<TransactionSynchronization> suspendedSynchronizations; 1294 1295 private String name; 1296 1297 private boolean readOnly; 1298 1299 private Integer isolationLevel; 1300 1301 private boolean wasActive; 1302 1303 private SuspendedResourcesHolder(Object suspendedResources) { 1304 this.suspendedResources = suspendedResources; 1305 } 1306 1307 private SuspendedResourcesHolder( 1308 Object suspendedResources, List<TransactionSynchronization> suspendedSynchronizations, 1309 String name, boolean readOnly, Integer isolationLevel, boolean wasActive) { 1310 1311 this.suspendedResources = suspendedResources; 1312 this.suspendedSynchronizations = suspendedSynchronizations; 1313 this.name = name; 1314 this.readOnly = readOnly; 1315 this.isolationLevel = isolationLevel; 1316 this.wasActive = wasActive; 1317 } 1318 } 1319 1320}