Статьи

Оптимистическая попытка блокировки с MongoDB

В моем предыдущем посте я говорил о преимуществах использования оптимистической блокировки для пакетных процессоров MongoDB. Как я уже писал ранее, исключение для оптимистической блокировки является восстанавливаемым, при условии, что мы получаем последнюю сущность, мы обновляем и сохраняем ее.

Поскольку мы используем MongoDB, нам не нужно беспокоиться о локальных транзакциях или транзакциях XA. В следующем посте я продемонстрирую, как вы можете создать такой же механизм при использовании JPA.

Среда Spring предлагает очень хорошую поддержку AOP и, следовательно, упрощает реализацию механизма автоматической повторной попытки, и я так и сделал.

Сначала мы определим аннотацию Retry:

1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Retry {
 
    Class<? extends Exception>[] on();
 
    int times() default 1;
}

и мы аннотируем наши методы бизнес-логики, такие как

1
2
3
4
5
6
7
@Retry(times = 10, on = org.springframework.dao.OptimisticLockingFailureException.class)
public Product updateName(Long id, String name) {
    Product product = productRepository.findOne(id);
    product.setName(name);
    LOGGER.info("Updating product {} name to {}", product, name);
    return productRepository.save(product);
}

Тогда нам нужен только AOP-аспект, чтобы перехватить вызовы бизнес-логики и повторить попытку в случае обнаружения оптимистической блокировки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Aspect
public class OptimisticConcurrencyControlAspect {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(OptimisticConcurrencyControlAspect.class);
 
    @Around("@annotation(vladmihalcea.concurrent.Retry)")
    public Object retry(ProceedingJoinPoint pjp) throws Throwable {
        Retry retryAnnotation = getRetryAnnotation(pjp);
        return (retryAnnotation != null) ? proceed(pjp, retryAnnotation) : proceed(pjp);
    }
 
    private Object proceed(ProceedingJoinPoint pjp) throws Throwable {
        return pjp.proceed();
    }
 
    private Object proceed(ProceedingJoinPoint pjp, Retry retryAnnotation) throws Throwable {
        int times = retryAnnotation.times();
        Class<? extends Throwable>[] retryOn = retryAnnotation.on();
        Assert.isTrue(times > 0, "@Retry{times} should be greater than 0!");
        Assert.isTrue(retryOn.length > 0, "@Retry{on} should have at least one Throwable!");
        LOGGER.info("Proceed with {} retries on {}", times, Arrays.toString(retryOn));
        return tryProceeding(pjp, times, retryOn);
    }
 
    private Object tryProceeding(ProceedingJoinPoint pjp, int times, Class<? extends Throwable>[] retryOn) throws Throwable {
        try {
            return proceed(pjp);
        } catch (Throwable throwable) {
            if(isRetryThrowable(throwable, retryOn) && times-- > 0) {
                LOGGER.info("Optimistic locking detected, {} remaining retries on {}", times, Arrays.toString(retryOn));
                return tryProceeding(pjp, times, retryOn);
            }
            throw throwable;
        }
    }
 
    private boolean isRetryThrowable(Throwable throwable, Class<? extends Throwable>[] retryOn) {
        Throwable[] causes = ExceptionUtils.getThrowables(throwable);
        for(Throwable cause : causes) {
            for(Class<? extends Throwable> retryThrowable : retryOn) {
                if(retryThrowable.isAssignableFrom(cause.getClass())) {
                    return true;
                }
            }
        }
        return false;
    }
 
    private Retry getRetryAnnotation(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Retry retryAnnotation = AnnotationUtils.findAnnotation(method, Retry.class);
 
        if(retryAnnotation != null) {
            return retryAnnotation;
        }
 
        Class[] argClasses = new Class[pjp.getArgs().length];
        for (int i = 0; i < pjp.getArgs().length; i++) {
            argClasses[i] = pjp.getArgs()[i].getClass();
        }
        method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argClasses);
        return AnnotationUtils.findAnnotation(method, Retry.class);
    }
}

Тест запускает 10 протекторов, борющихся за сохранение Продукта, и это протокол теста.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Line 492: INFO  [Thread-9]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 495: INFO  [Thread-3]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 504: INFO  [Thread-8]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 505: INFO  [Thread-11]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 507: INFO  [Thread-10]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 513: INFO  [Thread-5]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 523: INFO  [Thread-4]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 529: INFO  [Thread-3]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 586: INFO  [Thread-10]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 682: INFO  [Thread-5]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 683: INFO  [Thread-3]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 7 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 686: INFO  [Thread-8]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 702: INFO  [Thread-3]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 6 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 752: INFO  [Thread-5]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 7 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 756: INFO  [Thread-8]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 7 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
    Line 859: INFO  [Thread-5]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 6 remaining retries on [class org.springframework.dao.OptimisticLockingFailureException]
  • Код доступен на GitHub .