Автор оригинала: Vlad Mihalcea.
В своем предыдущем посте я говорил о преимуществах использования оптимистичной блокировки для пакетных процессоров MongoDB. Как я уже писал ранее, исключение оптимистической блокировки можно восстановить, если мы извлекаем последнюю сущность, обновляем и сохраняем ее.
Поскольку мы используем MongoDB, нам не нужно беспокоиться о локальных транзакциях или транзакциях XA. В следующем посте я продемонстрирую, как вы можете создать такой же механизм при использовании JPA.
Фреймворк Spring предлагает очень хорошую поддержку AOP и, следовательно, упрощает реализацию механизма автоматического повтора, и вот как я это сделал.
Сначала мы определяем аннотацию повторной попытки
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Retry { Class extends Exception>[] on(); int times() default 1; }
и мы аннотируем наши методы бизнес-логики, такие как
@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 для перехвата вызовов бизнес-логики и повторной попытки в случае обнаружения оптимистичной блокировки.
@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); } }
Эта утилита является частью моего проекта db-util вместе с механизмом повтора JPA оптимистичного управления параллелизмом.
Поскольку он уже доступен в Центральном репозитории Maven, вы можете легко использовать его, просто добавив эту зависимость в свой pom.xml:
com.vladmihalcea db-util 0.0.1
Тест запускает 10 потоков, конкурирующих за сохранение продукта, и это журнал тестирования.
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 .