Spring乐观锁:如何重试事务方法直到提交成功

发布于 2024-09-07 04:12:41 字数 2554 浏览 4 评论 0原文

我使用 Spring 2.5 和 Hibernate JPA 实现以及 Java 和“容器”管理事务。

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


我读了很多有关幂等的内容,如果 搜索 DEFAULT_MAX_RETRIES6.2.7。示例第14.5章。重试。我还在 stackoverflow 此处 和 < a href="http://www.springframework.net/doc-latest/reference/html/dao.html" rel="noreferrer">此处。

我尝试了这个:

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 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 。

I use Spring 2.5 and Hibernate JPA implementation with Java and "container" managed Transactions.

I have a "after user commit" method that updates data in background and need to be committed regardless of ConcurrencyFailureException or StaleObjectStateException exception, because it will never be shown to client. In other words, need to make Optimistic Lock to Pessimistic. (Could happen if methods execution will take little bit longer and someone changed data in other transaction)


I read a a lot about idempotent stuff, retry if exception in search for DEFAULT_MAX_RETRIES or 6.2.7. Example or chapter 14.5. Retry. I also found in stackoverflow here and here.

I tried this:

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 is my Annotation to mark methods that need to be retried, if a exception occurrs. Didn't work... I also tried several ways like SELECT ... FOR UPDATE, EntityManager.lock(...)

What is the best way to avoid stale data, dirty reads etc. such a strategy with Spring? Retry?, synchronized?, JPA lock?, isolation?, select ... for update? I could not get it to work and I really happy about any help.


Here is some pseudo code what I like to do:

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; 
}

Between // XXX and // YYY another session could modify the item, then the StaleObjectStateException gets thrown.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

征棹 2024-09-14 04:13:23

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

Throwing out another option here: BoneCP (http://jolbox.com) has support to automatically retry transactions upon failure (including when DB goes down, network fails, etc).

熊抱啵儿 2024-09-14 04:13:13

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

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

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

    并从 #2 重试

我们有一个重试计数器,用于在最后重新抛出异常。

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

We have this and what we do is:

  1. Flush the session (to make sure the upcoming update will be the only one queued)
  2. Load the instance
  3. Do the change
  4. On StaleObjectStateException, clear the action queue

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

    and retry from #2

We have a retry counter to re-throw the exception in the end.

NOTE: This is not an officially supported method (Hibernate clearly states that a session which has thrown an exception should be discarded and not re-used), but it's a known work-around (with the limitation that you can't selectively remove the update action, but must clear the whole queue).

若水般的淡然安静女子 2024-09-14 04:12:59

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

I got a solution but I think it's ugly. I catch all RuntimeException and it only works for new transactions. Do you know how to make it better? Do you see any problems?

First, I made an Annotation:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryingTransaction {
     int repeatCount() default 20;
}

Then I made a interceptor like this:

    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>

And a method annotated like this:

@RetryingTransaction
public Entity doSomethingInBackground(params)...
谢绝鈎搭 2024-09-14 04:12:54

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

Use Spring Retry to retry whole method when a version number or timestamp check failed (optimistic lock occurs).

Configuration

@Configuration
@EnableRetry
public class FooConfig {
     ...
}

Usage

@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

Project configuration

Spring Boot application has defined valid spring-retry version, so only this is required:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency> 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文