Annotation Interface RetryTransaction


@Documented @Inherited @Retention(RUNTIME) @Target({TYPE,METHOD}) public @interface RetryTransaction
An annotation for @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 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 @RetryTransaction annotation is ignored unless all of the following conditions are satisfied:

  • The method (and/or the containing type) must be annotated with both @Transactional and @RetryTransaction
  • The @Transactional annotation must have propagation set to either PROPAGATION_REQUIRED or PROPAGATION_REQUIRES_NEW (other propagation values do not involve creating new transactions).
  • In the case of 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 RetryTransactionAspect aspect (included in the dellroad-stuff JAR file).
  • The RetryTransactionAspect aspect must be configured with a PersistenceExceptionTranslator appropriate for the ORM layer being used.

    This is required because the RetryTransactionAspect doesn't know a priori which exceptions are "retryable" and which exceptions are hard errors. However, this is something that the PersistenceExceptionTranslator knows, because part of its job is wrapping lower-layer exceptions in org.springframework.dao exceptions, in particular 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 maxRetries(), initialDelay(), and 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 RetryTransactionProvider interface implemented by the aspect. 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 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 RetryTransactionProvider.retry().

See Also:
  • Field Details

    • DEFAULT_MAX_RETRIES

      static final int DEFAULT_MAX_RETRIES
      Default maximum number of retry attempts, used when the maxRetries value is not explicitly set in an instance of this annotation. This default value can be overridden by configuring the maxRetriesDefault property on the aspect itself.
      See Also:
    • DEFAULT_INITIAL_DELAY

      static final long DEFAULT_INITIAL_DELAY
      Default initial delay, in milliseconds, used when the initialDelay value is not explicitly set in an instance of this annotation. This default value can be overridden by configuring the initialDelayDefault property on the aspect itself.
      See Also:
    • DEFAULT_MAXIMUM_DELAY

      static final long DEFAULT_MAXIMUM_DELAY
      Default maximum delay, in milliseconds, used when the maximumDelay value is not explicitly set in an instance of this annotation. This default value can be overridden by configuring the maximumDelayDefault property on the aspect itself.
      See Also:
  • Element Details

    • maxRetries

      int maxRetries
      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 -1 indicates that the aspect-wide default value (4 by default), should be used.

      Returns:
      maximum number of transaction retry attempts
      Default:
      -1
    • initialDelay

      long initialDelay
      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 maximumDelay().

      If this property is not set explicitly, the default value of -1 indicates that the aspect-wide default value (100L milliseconds by default), should be used.

      Returns:
      initial delay between retry attempts in milliseconds
      Default:
      -1L
    • maximumDelay

      long maximumDelay
      The maximum delay between retry attempts in milliseconds. After the first transaction failure, we will pause for approximately 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 -1 indicates that the aspect-wide default value (30000L milliseconds by default), should be used.

      Returns:
      maximum delay between retry attempts in milliseconds
      Default:
      -1L