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

У нас есть это и что мы делаем:

  • Очистите сеанс (чтобы убедиться, что предстоящее обновление будет единственным в очереди)
  • Загрузите экземпляр
  • Сделайте изменение
  • В StaleObjectStateException очистите очередь действий

    ((EventSource) session).getActionQueue().clear()
    

    и повторите попытку С# 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 спускается, сеть не работает и т.д.).