
org.dellroad.stuff.spring.RetryTransactionAspect.aj Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dellroad-stuff-spring Show documentation
Show all versions of dellroad-stuff-spring Show documentation
DellRoad Stuff classes related to the Spring Framework.
The newest version!
/*
* Copyright (C) 2011 Archie L. Cobbs and other authors. All rights reserved.
*/
package org.dellroad.stuff.spring;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.function.Supplier;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.dao.UncategorizedDataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* An aspect that automatically retries failed but "retryable" transactions.
*
* @see RetryTransaction
*/
public aspect RetryTransactionAspect extends AbstractBean implements RetryTransactionProvider {
private final ThreadLocal> retryInfos = new ThreadLocal>() {
@Override
public ArrayDeque initialValue() {
return new ArrayDeque<>(2);
}
};
private final Random random = new Random();
// Default values for retry settings
private int maxRetriesDefault = RetryTransaction.DEFAULT_MAX_RETRIES;
private long initialDelayDefault = RetryTransaction.DEFAULT_INITIAL_DELAY;
private long maximumDelayDefault = RetryTransaction.DEFAULT_MAXIMUM_DELAY;
// This knows how to read information from @Transactional annotation
private final AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(false);
// Tells us what is a "transient" exception; must be explicitly configured
private PersistenceExceptionTranslator persistenceExceptionTranslator;
// Property accessors and RetryTransactionProvider implementation
@Override
public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
return this.persistenceExceptionTranslator;
}
@Override
public void setPersistenceExceptionTranslator(PersistenceExceptionTranslator persistenceExceptionTranslator) {
this.persistenceExceptionTranslator = persistenceExceptionTranslator;
}
@Override
public int getMaxRetriesDefault() {
return this.maxRetriesDefault;
}
@Override
public void setMaxRetriesDefault(int maxRetriesDefault) {
this.maxRetriesDefault = maxRetriesDefault;
}
@Override
public long getInitialDelayDefault() {
return this.initialDelayDefault;
}
@Override
public void setInitialDelayDefault(long initialDelayDefault) {
this.initialDelayDefault = initialDelayDefault;
}
@Override
public long getMaximumDelayDefault() {
return this.maximumDelayDefault;
}
@Override
public void setMaximumDelayDefault(long maximumDelayDefault) {
this.maximumDelayDefault = maximumDelayDefault;
}
@Override
public int getAttemptNumber() {
return this.getAttemptNumber(null);
}
@Override
public int getAttemptNumber(String transactionManagerName) {
final ArrayDeque retryInfoStack = this.retryInfos.get();
for (Iterator i = retryInfoStack.descendingIterator(); i.hasNext(); ) {
final RetryInfo retryInfo = i.next();
if (transactionManagerName == null || transactionManagerName.equals(retryInfo.getTransactionManagerName()))
return retryInfo.getAttemptNumber();
}
return 0;
}
// InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
if (this.persistenceExceptionTranslator == null)
throw new IllegalArgumentException("no PersistenceExceptionTranslator configured");
}
// Aspect
// Ensure that this aspect is woven outside of the AnnotationTransactionAspect but inside of AnnotationAsyncExecutionAspect
declare precedence :
org.springframework.scheduling.aspectj.*, RetryTransactionAspect, org.springframework.transaction.aspectj.*;
/**
* Attempt to execute a transactional method. If the method throws any exception which is translated
* to a subclass of TransientDataAccessException, the method will be executed again, with a delay
* between attempts.
*/
Object around(final Object txObject) : retryTransactionalMethodExecution(txObject) {
// Get method info
final MethodSignature methodSignature = (MethodSignature)thisJoinPoint.getSignature();
final Method method = methodSignature.getMethod();
// Get transaction attribute from @Transactional annotation
final TransactionAttribute transactionAttribute = this.transactionAttributeSource
.getTransactionAttribute(method, txObject.getClass());
if (transactionAttribute == null) {
throw new RuntimeException("no @Transactional annotation found for method "
+ method + "; required for @RetryTransaction");
}
final String transactionManagerName = transactionAttribute.getQualifier();
final String description = "@Transactional method " + method;
// Do nothing unless this method is about to create a new transaction
switch (transactionAttribute.getPropagationBehavior()) {
case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
break;
case TransactionDefinition.PROPAGATION_REQUIRED:
if (TransactionSynchronizationManager.isActualTransactionActive()
&& this.getAttemptNumber(transactionManagerName) > 0) {
if (this.log.isTraceEnabled()) {
this.log.trace("skipping retry logic; transaction already open for {} in {}",
transactionManagerName, description);
}
return proceed(txObject);
}
break;
default:
return proceed(txObject);
}
// Find @RetryTransaction annotation (there should always be one unless this aspect was extended)
RetryTransaction retryTransaction = AnnotationUtils.getAnnotation(method, RetryTransaction.class);
if (retryTransaction == null)
retryTransaction = AnnotationUtils.findAnnotation(method.getDeclaringClass(), RetryTransaction.class);
// Setup retry and perform attempt(s)
return this.retry(new RetrySetup
© 2015 - 2025 Weber Informatics LLC | Privacy Policy