@Documented @Inherited @Retention(value=RUNTIME) @Target(value={TYPE,METHOD}) public @interface RetryTransaction
@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:
@Transactional
and @RetryTransaction
@Transactional annotation must have
propagation set to either
PROPAGATION_REQUIRED or
PROPAGATION_REQUIRES_NEW
(other propagation values do not involve creating new transactions).
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.
RetryTransactionAspect aspect (included in the dellroad-stuff JAR file).
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.getAttempt();
...
}
You can also invoke the retry logic directly (i.e., without going through a method woven with the aspect); see
RetryTransactionProvider.retry().
RetryTransactionProvider,
Transactional| Modifier and Type | Fields and Description |
|---|---|
static long |
DEFAULT_INITIAL_DELAY
Default initial delay, in milliseconds, used when the
initialDelay
value is not explicitly set in an instance of this annotation. |
static 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. |
static long |
DEFAULT_MAXIMUM_DELAY
Default maximum delay, in milliseconds, used when the
maximumDelay
value is not explicitly set in an instance of this annotation. |
| Modifier and Type | Optional Element and Description |
|---|---|
long |
initialDelay
The initial delay between retry attempts in milliseconds.
|
long |
maximumDelay
The maximum delay between retry attempts in milliseconds.
|
int |
maxRetries
The maximum number of transaction retry attempts.
|
public static final int DEFAULT_MAX_RETRIES
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.public static final long DEFAULT_INITIAL_DELAY
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.public static final long DEFAULT_MAXIMUM_DELAY
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.public abstract int maxRetries
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.
public abstract long initialDelay
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.
public abstract long maximumDelay
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.
Copyright © 2020. All rights reserved.