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