【问题标题】:Spring Optimistic Locking:How to retry transactional method till commit is successfulSpring Optimistic Locking:如何重试事务方法直到提交成功
【发布时间】:2011-03-09 19:27:39
【问题描述】:

我将 Spring 2.5 和 Hibernate JPA 实现与 Java 和“容器”托管事务一起使用。

我有一个“用户提交后”方法,它在后台更新数据,无论ConcurrencyFailureExceptionStaleObjectStateException 异常,都需要提交,因为它永远不会显示给客户端。换句话说,需要把乐观锁变成悲观。 (如果方法执行需要更长的时间并且有人在其他事务中更改了数据,则可能会发生)


我读了很多关于幂等的东西,如果 search for DEFAULT_MAX_RETRIES6.2.7. Examplechapter 14.5. Retry 出现异常,请重试。我还在stackoverflow 中找到了herehere

我试过这个:

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 UPDATEEntityManager.lock(...)

使用 Spring 避免过时数据、脏读等策略的最佳方法是什么?重试?,同步?,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。

【问题讨论】:

  • 您的 2 个链接用于 Spring.net
  • 我知道,但他们以同样的方式解决了这个问题......
  • 我需要说的是,我认为的方面,被执行到“早期”,事务的提交发生在后面,所以重试是不可能的。今晚我还尝试了 select ... for update,锁工作了,但两个客户端都收到了乐观锁异常(或陈旧数据)。
  • 我发现了露天风格:google.com/codesearch/p?hl=de#xjl_JOiZ61E/repos/… 有这样的标准吗?

标签: java spring transactions aspectj optimistic-locking


【解决方案1】:

当版本号或时间戳检查失败(发生乐观锁)时,使用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

项目配置

Spring Boot 应用已经定义了有效的 spring-retry 版本,所以只有这个是必需的:

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

【讨论】:

    【解决方案2】:

    我们有这个,我们要做的是:

    1. 刷新会话(以确保即将到来的更新将是唯一排队的更新)
    2. 加载实例
    3. 进行更改
    4. 在 StaleObjectStateException 上,清除操作队列

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

      然后从 #2 重试

    我们有一个重试计数器来重新抛出异常。

    注意:这不是官方支持的方法(Hibernate 明确指出应该丢弃抛出异常的会话并且不重新使用),但它是一种已知的解决方法(使用您不能选择性地删除更新操作,但必须清除整个队列的限制)。

    【讨论】:

      【解决方案3】:

      在这里抛出另一个选项:BoneCP (http://jolbox.com) 支持在失败时自动重试事务(包括当 DB 出现故障、网络故障等时)。

      【讨论】:

      • 应该的。它只是重播连接/语句句柄的任何内容。
      • 但是更新查询将包含陈旧的乐观锁值并再次失败。
      【解决方案4】:

      我有一个解决方案,但我认为它很难看。我捕获了所有 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)...
      

      【讨论】:

      • knarf1983,我认为您的解决方案很棒,我希望它被添加到 Hibernate/Spring 中。不过,我有一个问题:我们是否必须清除休眠会话以确保新事务将从数据库中加载新值?另外我认为捕获 RuntimeException 太笼统了,无法重试!你为什么不坚持“org.hibernate.StaleObjectStateException”?
      猜你喜欢
      • 1970-01-01
      • 2019-03-30
      • 1970-01-01
      • 1970-01-01
      • 2014-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-14
      相关资源
      最近更新 更多