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

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

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


我读了很多关于幂等的东西,如果exception搜索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,用于标记需要重试的方法(如果发生exception)。 没工作……我也尝试了几种方式,如SELECT ... FOR UPDATEEntityManager.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。

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

    SYNCHRONIZATION_ALWAYS            

并且注释方法如下:

 @RetryingTransaction public Entity doSomethingInBackground(params)... 

当版本号或时间戳检查失败时(发生乐观锁定),使用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版本,因此只需要这样:

  org.springframework.retry spring-retry  

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

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

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

    并从#2重试

我们有一个重试计数器,最后重新抛出exception。

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

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