В моем предыдущем посте я говорил о преимуществах использования оптимистической блокировки для пакетных процессоров 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
|
@Aspectpublic 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 .