All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.stuff.spring.RetryTransaction Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2022 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.stuff.spring;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * An annotation for {@link org.springframework.transaction.annotation.Transactional @Transactional} methods that
 * want to have transactions automatically retried when they fail due to a transient exception. A transient exception
 * is one that Spring would translate into a
 * {@link org.springframework.dao.TransientDataAccessException TransientDataAccessException}.
 *
 * 
 * 
 * 
 * 
 *
 * 

* This automatic retry logic is very handy for solving the problem of transient deadlocks that can occur in complex Java/ORM * applications. Due to the ORM layer hiding the details of the underlying data access patterns, it's often difficult * to design Java/ORM applications such that transient deadlocks at the database layer can't occur. Since these * deadlocks can often be dealt with simply by retrying the transaction, having retry logic automatically applied can * eliminate this problem. * *

* Note, beans involved in transactions should either be stateless, or be prepared to rollback any state changes on transaction * failure; of course, this is true whether or not transactions are automatically being retried, but adding automatic retry * can magnify pre-existing bugs of that nature. * *

* The {@link RetryTransaction @RetryTransaction} annotation is ignored unless all of the following conditions are satisfied: *

    *
  • * The method (and/or the containing type) must be annotated with both * {@link org.springframework.transaction.annotation.Transactional @Transactional} * and {@link RetryTransaction @RetryTransaction} *
  • *
  • * The {@link org.springframework.transaction.annotation.Transactional @Transactional} annotation must have * {@linkplain org.springframework.transaction.annotation.Transactional#propagation propagation} set to either * {@link org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED PROPAGATION_REQUIRED} or * {@link org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW PROPAGATION_REQUIRES_NEW} * (other propagation values do not involve creating new transactions). *
  • *
  • * In the case of * {@link org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED PROPAGATION_REQUIRED} propagation, * there must not be a transaction already open in the calling thread (under the same transaction manager). In other words, * the invoked method must be the one responsible for creating a new transaction. *
  • *
  • * The method's class must be woven (either at build time or runtime) using the * AspectJ compiler * with the {@code RetryTransactionAspect} aspect (included in the dellroad-stuff JAR file). *
  • *
  • * The {@code RetryTransactionAspect} aspect must be configured with a * {@link org.springframework.dao.support.PersistenceExceptionTranslator PersistenceExceptionTranslator} appropriate for * the ORM layer being used. * *

    * This is required because the {@code RetryTransactionAspect} doesn't know a priori which exceptions are * "retryable" and which exceptions are hard errors. However, this is something that the * {@link org.springframework.dao.support.PersistenceExceptionTranslator PersistenceExceptionTranslator} knows, * because part of its job is wrapping lower-layer exceptions in {@link org.springframework.dao} exceptions, * in particular {@link org.springframework.dao.TransientDataAccessException TransientDataAccessException}. * *

    * The simplest way to do this is to include the aspect in your Spring application context, for example: * *

    
     *      <bean class="org.dellroad.stuff.spring.RetryTransactionAspect" factory-method="aspectOf"
     *        p:persistenceExceptionTranslator-ref="myJpaDialect"/>
     *      
    * *

    * This also gives you the opportunity to change the default values for {@link #maxRetries}, {@link #initialDelay}, * and {@link #maximumDelay}, which are applied when not explicitly overridden in the annotation, for example: * *

    
     *      <bean class="org.dellroad.stuff.spring.RetryTransactionAspect" factory-method="aspectOf"
     *        p:persistenceExceptionTranslator-ref="myJpaDialect" p:maxRetriesDefault="2"
     *        p:initialDelayDefault="25" p:maximumDelayDefault="5000"/>
     *      
    *
  • *
* *

* Logging behavior: Normal activity is logged at trace level, retries are logged at debug level, and errors are logged * at error level. * *

* Transactional code can determine the transaction attempt number using the {@link RetryTransactionProvider} interface * implemented by the aspect. {@link RetryTransactionProvider#getAttemptNumber} method returns the current attempt number * (1, 2, 3...), or zero if the current thread is not executing within activated retry logic: *


 *      import org.dellroad.stuff.spring.RetryTransactionProvider;
 *      ...
 *
 *      @Autowired
 *      private RetryTransactionProvider retryTransactionProvider;
 *      ...
 *
 *      @RetryTransaction
 *      @Transactional
 *      public void doSomething() {
 *          ...
 *          final int attempt = this.retryTransactionProvider.getAttemptNumber();
 *          ...
 *      }
 * 
* *

* You can acquire also the singleton {@link RetryTransactionProvider} instance directly like this: *


 *      import org.dellroad.stuff.spring.RetryTransactionAspect;
 *      import org.dellroad.stuff.spring.RetryTransactionProvider;
 *      ...
 *
 *      @RetryTransaction
 *      @Transactional
 *      public void doSomething() {
 *          ...
 *          final RetryTransactionProvider rtp = RetryTransactionAspect.aspectOf();
 *          final int attempt = rtp.getAttemptNumber();
 *          ...
 *      }
 * 
* *

* You can also invoke the retry logic directly (i.e., without going through a method woven with the aspect); see * {@link RetryTransactionProvider#retry RetryTransactionProvider.retry()}. * * @see RetryTransactionProvider * @see org.springframework.transaction.annotation.Transactional */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public @interface RetryTransaction { /** * Default {@linkplain #maxRetries maximum number of retry attempts}, used when the {@link #maxRetries maxRetries} * value is not explicitly set in an instance of this annotation. * This default value can be overridden by configuring the {@code maxRetriesDefault} property on the aspect itself. */ int DEFAULT_MAX_RETRIES = 4; /** * Default {@linkplain #initialDelay initial delay}, in milliseconds, used when the {@link #initialDelay initialDelay} * value is not explicitly set in an instance of this annotation. * This default value can be overridden by configuring the {@code initialDelayDefault} property on the aspect itself. */ long DEFAULT_INITIAL_DELAY = 100; /** * Default {@linkplain #maximumDelay maximum delay}, in milliseconds, used when the {@link #maximumDelay maximumDelay} * value is not explicitly set in an instance of this annotation. * This default value can be overridden by configuring the {@code maximumDelayDefault} property on the aspect itself. */ long DEFAULT_MAXIMUM_DELAY = 30 * 1000; /** * The maximum number of transaction retry attempts. * *

* If the transaction fails, it will be retried at most this many times. * This limit applies to retries only; it does not apply to the very first attempt, which is always made. * So a value of zero means at most one attempt. * *

* If this property is not set explicitly, the default value of {@code -1} indicates that the aspect-wide default value * ({@value #DEFAULT_MAX_RETRIES} by default), should be used. * * @return maximum number of transaction retry attempts */ int maxRetries() default -1; /** * The initial delay between retry attempts in milliseconds. * After the first transaction failure, we will pause for approximately this many milliseconds. * For additional failures we apply a randomized exponential back-off, up to a maximum of {@link #maximumDelay}. * *

* If this property is not set explicitly, the default value of {@code -1} indicates that the aspect-wide default value * ({@value #DEFAULT_INITIAL_DELAY} milliseconds by default), should be used. * * @return initial delay between retry attempts in milliseconds */ long initialDelay() default -1; /** * The maximum delay between retry attempts in milliseconds. * After the first transaction failure, we will pause for approximately {@link #initialDelay} milliseconds. * For additional failures we apply a randomized exponential back-off, up to a maximum of this value. * *

* If this property is not set explicitly, the default value of {@code -1} indicates that the aspect-wide default value * ({@value #DEFAULT_MAXIMUM_DELAY} milliseconds by default), should be used. * * @return maximum delay between retry attempts in milliseconds */ long maximumDelay() default -1; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy