Annotation 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:
-
The method (and/or the containing type) must be annotated with both
@Transactional
and@RetryTransaction
-
The
@Transactional
annotation must have propagation set to eitherPROPAGATION_REQUIRED
orPROPAGATION_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 thedellroad-stuff
JAR file). -
The
RetryTransactionAspect
aspect must be configured with aPersistenceExceptionTranslator
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 thePersistenceExceptionTranslator
knows, because part of its job is wrapping lower-layer exceptions inorg.springframework.dao
exceptions, in particularTransientDataAccessException
.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()
, andmaximumDelay()
, 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:
-
Optional Element Summary
Modifier and TypeOptional ElementDescriptionlong
The initial delay between retry attempts in milliseconds.long
The maximum delay between retry attempts in milliseconds.int
The maximum number of transaction retry attempts. -
Field Summary
Modifier and TypeFieldDescriptionstatic final long
Default initial delay, in milliseconds, used when theinitialDelay
value is not explicitly set in an instance of this annotation.static final int
Default maximum number of retry attempts, used when themaxRetries
value is not explicitly set in an instance of this annotation.static final long
Default maximum delay, in milliseconds, used when themaximumDelay
value is not explicitly set in an instance of this annotation.
-
Field Details
-
DEFAULT_MAX_RETRIES
static final int DEFAULT_MAX_RETRIESDefault maximum number of retry attempts, used when themaxRetries
value is not explicitly set in an instance of this annotation. This default value can be overridden by configuring themaxRetriesDefault
property on the aspect itself.- See Also:
-
DEFAULT_INITIAL_DELAY
static final long DEFAULT_INITIAL_DELAYDefault initial delay, in milliseconds, used when theinitialDelay
value is not explicitly set in an instance of this annotation. This default value can be overridden by configuring theinitialDelayDefault
property on the aspect itself.- See Also:
-
DEFAULT_MAXIMUM_DELAY
static final long DEFAULT_MAXIMUM_DELAYDefault maximum delay, in milliseconds, used when themaximumDelay
value is not explicitly set in an instance of this annotation. This default value can be overridden by configuring themaximumDelayDefault
property on the aspect itself.- See Also:
-
-
Element Details
-
maxRetries
int maxRetriesThe 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 initialDelayThe 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 ofmaximumDelay()
.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 maximumDelayThe maximum delay between retry attempts in milliseconds. After the first transaction failure, we will pause for approximatelyinitialDelay()
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
-