복붙노트

[SPRING] Spring Optimistic Locking : 커밋이 성공할 때까지 트랜잭션 방식을 다시 시도하는 방법

SPRING

Spring Optimistic Locking : 커밋이 성공할 때까지 트랜잭션 방식을 다시 시도하는 방법

Spring 2.5와 Java와 "컨테이너"관리 트랜잭션을 가진 Hibernate JPA 구현을 사용한다.

"백그라운드에서 데이터를 업데이트하고 클라이언트에 표시되지 않기 때문에 ConcurrencyFailureException 또는 StaleObjectStateException 예외에 관계없이 커밋해야하는"사용자 커밋 후 "메서드가 있습니다. 즉, 낙관적 인 전망을 낙관적으로 만들 필요가 있습니다. (메소드 실행이 조금 더 오래 걸리고 누군가 다른 트랜잭션에서 데이터를 변경하면 발생할 수 있습니다)

Idempotent 물건에 대해 많이 읽었고 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 예외가 발생하는 경우 다시 시도해야하는 메서드를 표시하는 내 Annotation입니다. 작동하지 않았다 ... 나는 또한 SELECT ... FOR UPDATE, EntityManager.lock (...)과 같은 여러 가지 방법을 시도했다.

부실 데이터, 더티 읽기 등을 피하는 가장 좋은 방법은 무엇입니까? 재 시도?, 동기화?, JPA 잠금?, 격리?, 선택 ... ... 갱신? 나는 그것을 작동시키지 못했고 어떤 도움이든 정말 행복합니다.

다음은 내가하고 싶은 의사 코드입니다 :

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. ==============================

    1.나는 해결책을 얻었지만 추악하다고 생각한다. 모든 RuntimeException을 잡아서 새로운 트랜잭션에만 적용됩니다. 더 나은 방법을 알고 있습니까? 어떤 문제가 보이십니까?

    나는 해결책을 얻었지만 추악하다고 생각한다. 모든 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. ==============================

    2.버전 번호 또는 타임 스탬프 검사가 실패한 경우 (낙관적 인 잠금이 발생하는 경우) 전체 재 시도를 재 시도하려면 Spring Retry를 사용하십시오.

    버전 번호 또는 타임 스탬프 검사가 실패한 경우 (낙관적 인 잠금이 발생하는 경우) 전체 재 시도를 재 시도하려면 Spring Retry를 사용하십시오.

    @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
    

    스프링 부트 응용 프로그램은 유효한 스프링 재시도 버전을 정의 했으므로 다음이 필요합니다.

    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
    </dependency> 
    
  3. ==============================

    3.우리는 이것을 가지고 있고 우리가하는 일은 :

    우리는 이것을 가지고 있고 우리가하는 일은 :

    마지막으로 예외를 다시 던지기위한 재시도 카운터가 있습니다.

    주의 : 이것은 공식적으로 지원되는 방법은 아니다. (Hibernate는 예외를 던진 세션이 폐기되어야하고 재사용되지 않아야 함을 분명히 말하고있다.) 그러나 알려진 해결 방법이다. 작업을 업데이트하지만 전체 대기열을 지워야 함).

  4. ==============================

    4.여기에 또 다른 옵션을 던지기 : BoneCP (http://jolbox.com)는 장애가 발생했을 때 (DB가 다운되었을 때, 네트워크 장애 등을 포함하여) 트랜잭션을 자동으로 재 시도하도록 지원합니다.

    여기에 또 다른 옵션을 던지기 : BoneCP (http://jolbox.com)는 장애가 발생했을 때 (DB가 다운되었을 때, 네트워크 장애 등을 포함하여) 트랜잭션을 자동으로 재 시도하도록 지원합니다.

  5. from https://stackoverflow.com/questions/3144010/spring-optimistic-lockinghow-to-retry-transactional-method-till-commit-is-succe by cc-by-sa and MIT license