乐观锁的重试机制(spring data + JPA)

问题描述 投票:0回答:4

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

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

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

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

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

所以:

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

2)该解决方案有任何文档\教程吗?

3)是否可以重试 @Transactional 注解的方法?

干杯!

java spring hibernate jpa optimistic-locking
4个回答
12
投票

广告3.

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

配置

@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)
仅重试带注释的方法中的代码。


2
投票

您有两种方法可以实现这一目标,如下

从休眠乐观锁定异常中恢复

使用Spring AOP重试失败的幂等并发操作

希望这会帮助你..!


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 
}

0
投票

这张票中的答案对我帮助很大,但我想添加我的答案来强调我犯的一个具体错误:

  • 可重试的方法必须驻留在新类中
  • 新类必须是 Bean(例如,该类是 @Component 注解的)

否则SpringBoot没有做任何代理,方法就按原样调用,也没有重试。

类似这样的:

import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class MyRetryer {

    // * must be public because of @Transactional
    // * must be in a separate class or REQUIRES_NEW has no effect due to framework proxying
    @Retryable(maxAttempts = 3, value = OptimisticLockingFailureException.class)
    @Transactional(propagation = REQUIRES_NEW)
    public void trySavingThingsToTheDatabase(SomeEntity entity) {
        log.debug("trySavingThingsToTheDatabase: start");
        try {
            someRepository.save(entity);
            log.debug("trySavingThingsToTheDatabase: success");
        } catch (OptimisticLockingFailureException e) {
            log.debug("trySavingThingsToTheDatabase: temporary exception", e);
            // ignore + try again using Spring Retry
            throw e;
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.