乐观锁重试机制(Spring Data + JPA)

12

为了提高并发性而不使用悲观锁,我们决定在我们的Web应用程序中使用乐观锁。

现在我们正在寻找重试解决方案。

我们希望对我们当前的代码库影响尽可能小。

我们在网上看到的解决方案之一是使用带有注释的重试拦截器来标记方法可重试。

问题是,我们想要注释那些带有 @Transactional 注释的方法,但拦截器无法重试它们某个原因。(拦截器能够完美地重试非事务方法。)

所以:

1)是否有任何替代方案,使重试对我们的代码影响最小?

2)是否有针对该解决方案的文档或教程?

3)甚至有可能重试 @Transactional 注释的方法吗?

干杯!

3个回答

11

广告3。

您可以使用Spring Retry在事务方法失败时进行重试,当版本号或时间戳检查失败(乐观锁发生)时进行重试。

配置

@Configuration
@EnableRetry
public class RetryConfig {

}

使用方法

@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

使用 @Transactional(propagation=Propagation.REQUIRES_NEW) 仅对带注释方法中的代码进行重试。


在关闭时发生了“SQLException: Statement closed.”。 - xiemeilong
2
@xiemeilong 我和你一样在使用Hibernate 5.0.x时遇到了同样的错误。使用版本为5.2.14.Final时,该问题消失了。我花了将近一天的时间才弄清楚这个问题。 :( - Jean-François Beauchef
1
@Jean-FrançoisBeauchef 非常感谢,我稍后会尝试一下。 - xiemeilong

2

Ashish,我们已经尝试了第二种解决方案,显然在事务方法中使用它时它不起作用,你有任何想法吗? - Urbanleg
我完全按照http://josiahgore.blogspot.in/2011/02/using-spring-aop-to-retry-failed.html中所写的步骤进行操作,然后我使用了@RetryConcurrentOperation注解来标记一个服务方法,其中包括exception = HibernateOptimisticLockingFailureException.class和retries = 12。 现在,由于该方法被@Transactional注解,它无法正常工作,会出现常规的乐观锁异常。如果我将其注释为非事务性方法,则可以正常工作。 - Urbanleg

0
你的重试无法生效的原因是@Transactional的优先级高于@Aspect。
你应该在TryAgainAspect类中实现Ordered,使@Aspect的优先级更高。
拦截器类:
@Aspect
@Component
public class TryAgainAspect implements Ordered {

private int maxRetries;
private int order = 1;

public void setMaxRetries(int maxRetries) {
    this.maxRetries = maxRetries;
}

public int getOrder() {
    return this.order;
}

@Pointcut("@annotation(IsTryAgain)")
public void retryOnOptFailure() {
}

@Around("retryOnOptFailure()")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    MethodSignature msig = (MethodSignature) pjp.getSignature();
    Object target = pjp.getTarget();
    Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
    IsTryAgain annotation = currentMethod.getAnnotation(IsTryAgain.class);
    this.setMaxRetries(annotation.tryTimes());

    int numAttempts = 0;
    do {
        numAttempts++;
        try {
            return pjp.proceed();
        } catch (ObjectOptimisticLockingFailureException | StaleObjectStateException exception) {
            exception.printStackTrace();
            if (numAttempts > maxRetries) {
                throw new NoMoreTryException("System error, all retry failed");
            } else {
                System.out.println("0 === retry ===" + numAttempts + "times");
            }
        }
    } while (numAttempts <= this.maxRetries);

    return null;
}

}

是否重试:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IsTryAgain {
    int tryTimes() default 5;
}

您的服务类方法应该添加注释@IsTryAgain和@Transactional。
@IsTryAgain
@Transactional(rollbackFor = Exception.class)
public Product buyProduct(Product product) {
// your business logic 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接