Spring Оптимистическая блокировка: как повторить транзакционный метод до фиксации успешно
Я использую Spring 2.5 и Hibernate JPA с Java и управляемыми транзакциями.
У меня есть метод "после пользователя", который обновляет данные в фоновом режиме и должен выполняться независимо от исключения ConcurrencyFailureException
или StaleObjectStateException
, потому что он никогда не будет показан клиенту. Другими словами, нужно сделать оптимистическую блокировку пессимистичной. (Может случиться, что если выполнение методов займет немного больше времени, а кто-то изменил данные в другой транзакции)
Я много читал об идемпотентном материале, повторю, если исключение в искать DEFAULT_MAX_RETRIES или 6.2.7. Пример или глава 14.5. Повторите попытку. Я также нашел в stackoverflow здесь и здесь.
Я пробовал это:
public aspect RetryOnConcurrencyExceptionAspect {
private static final int DEFAULT_MAX_RETRIES = 20;
private int maxRetries = DEFAULT_MAX_RETRIES;
Object around(): execution( * * (..) ) && @annotation(RetryOnConcurrencyException) && @annotation(Transactional) {
int numAttempts = 0;
RuntimeException failureException = null;
do {
numAttempts++;
try {
return proceed();
}
catch( OptimisticLockingFailureException ex ) {
failureException = ex;
}
catch(ConcurrencyFailureException ex) {
failureException = ex;
}
catch( StaleObjectStateException ex) {
failureException = ex;
}
} while( numAttempts <= this.maxRetries );
throw failureException;
}
}
RetryOnConcurrencyException
- это моя аннотация, чтобы отмечать методы, которые необходимо повторить, если возникает исключение. Не работает... Я также пробовал несколько способов: SELECT ... FOR UPDATE
, EntityManager.lock(...)
Каков наилучший способ избежать устаревших данных, грязных чтений и т.д. такой стратегии с помощью Spring? Retry?, synchronized?, JPA lock?, isol?, select... для обновления? Я не мог заставить его работать, и я очень рад любой помощи.
Вот какой псевдо-код мне нравится:
void doSomething(itemId) {
select something into A;
select anotherthing into B;
// XXX
item = getItemFormDB( itemId ); // takes long for one user and for other concurrent user it could take less time
item.setA(A);
item.setB(B);
// YYYY
update item;
}
Между//XXX и //YYY другой сеанс может изменить элемент, затем вызывается StaleObjectStateException.
Ответы
Ответ 1
У меня есть решение, но я считаю это уродливым. Я улавливаю все RuntimeException и работает только для новых транзакций. Вы знаете, как сделать это лучше? Вы видите какие-то проблемы?
Сначала я сделал аннотацию:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryingTransaction {
int repeatCount() default 20;
}
Тогда я сделал перехватчик следующим образом:
public class RetryingTransactionInterceptor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 20;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
@Resource
private PlatformTransactionManager transactionManager;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object retryOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
Exception failureException = null;
do {
numAttempts++;
try {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus status = transactionManager.getTransaction(def);
Object obj = pjp.proceed();
transactionManager.commit(status);
return obj;
}
catch( RuntimeException re ) {
failureException = re;
}
} while( numAttempts <= this.maxRetries );
throw failureException;
}
}
Spring applicationConfig.xml:
<tx:annotation-driven transaction-manager="transactionManager" order="10" />
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionSynchronizationName">
<value>SYNCHRONIZATION_ALWAYS</value>
</property>
</bean>
<bean id="retryingTransactionInterceptor" class="com.x.y.z.transaction.RetryingTransactionInterceptor">
<property name="order" value="1" />
</bean>
<aop:config>
<aop:aspect id="retryingTransactionAspect" ref="retryingTransactionInterceptor">
<aop:pointcut
id="servicesWithRetryingTransactionAnnotation"
expression="execution( * com.x.y.z.service..*.*(..) ) and @annotation(com.x.y.z.annotation.RetryingTransaction)"/>
<aop:around method="retryOperation" pointcut-ref="servicesWithRetryingTransactionAnnotation"/>
</aop:aspect>
</aop:config>
И метод аннотируется следующим образом:
@RetryingTransaction
public Entity doSomethingInBackground(params)...
Ответ 2
У нас есть это и что мы делаем:
У нас есть счетчик повторных попыток для повторного выброса исключения в конце.
ПРИМЕЧАНИЕ.. Это не официально поддерживаемый метод (Hibernate четко заявляет, что сеанс, который выбрал исключение, должен быть отброшен и не использоваться повторно), но это известная работа (с ограничение, которое вы не можете выборочно удалить действие обновления, но должно очистить всю очередь).
Ответ 3
Используйте Spring Повторить попытку, чтобы повторить весь метод, когда номер версии или отметка времени не удалась (происходит оптимистическая блокировка).
Конфигурация
@Configuration
@EnableRetry
public class FooConfig {
...
}
Использование
@Retryable(StaleStateException.class)
@Transactional
public void doSomethingWithFoo(Long fooId){
// read your entity again before changes!
Foo foo = fooRepository.findOne(fooId);
foo.setStatus(REJECTED) // <- sample foo modification
} // commit on method end
Конфигурация проекта
Spring Загрузочное приложение определило допустимую версию spring -retry, поэтому требуется только следующее:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
Ответ 4
Выбрасывая еще один вариант здесь: BoneCP (http://jolbox.com) поддерживает автоматическую повторную транзакцию при сбое (в том числе, когда DB спускается, сеть не работает и т.д.).