Мне трудно справиться с ObjectOptimisticLockingFailureException
в моем весеннем загрузочном проекте.
Мое приложение основано на микросервисной архитектуре, и службы взаимодействуют асинхронно через посредник сообщений RabbitMQ.
Здесь, в моем классе обслуживания, выдается исключение, поскольку он пытается обновить сущность Appointment
, которая имеет свойство intern @Version
:
@Service
@Slf4j
@RequiredArgsConstructor
public class AppointmentServiceImpl implements AppointmentService {
private final PatientRepository myPatientRepo;
private final ProducerService myRabbitMQProducer;
private final AppointmentRepository myAppointmentRepo;
private ModelMapper modelMapper=new ModelMapper() ;
@Override
@Transactional(noRollbackFor = {org.hibernate.StaleObjectStateException.class,ObjectOptimisticLockingFailureException.class,org.hibernate.dialect.lock.OptimisticEntityLockException.class})
public AppointmentDTO handleFinancialResultReception(AppointmentDTO myAppointment) {
Appointment myExistingAppointment=modelMapper.map(myAppointment, Appointment.class);
//check if patient is financial covered
if(myAppointment.getPatient().isFinancialApproved()) {
//if so set state to FINANCIAL_APPROVED
myExistingAppointment.setState(AppointmentState.FINANCIAL_APPROVED);
myExistingAppointment.setRejectionReason(myAppointment.getRejectionReason());
return modelMapper.map(myAppointmentRepo.save(myExistingAppointment),AppointmentDTO.class);
}else {
myExistingAppointment.setState(AppointmentState.REJECTED);
myExistingAppointment.setRejectionReason(myAppointment.getRejectionReason());
return modelMapper.map(myAppointmentRepo.save(myExistingAppointment),AppointmentDTO.class);
}
}
}
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
}
Эта служба вызывается из сообщения потребитель, который выглядит следующим образом:
private final ModelMapper modelMapper=new ModelMapper();
private final AppointmentService myAppointmentService;
@RabbitListener(queues = "${queue.financial.result}")
@Transactional(noRollbackFor = {org.hibernate.StaleObjectStateException.class,ObjectOptimisticLockingFailureException.class,org.hibernate.dialect.lock.OptimisticEntityLockException.class})
public void handleQueueFinancialResultMessageReception(AppointmentPayloadDTO myPayload) {
log.info(" ============================ Message received in Queue queue-check-fin-result : " + myPayload);
myPayload.setExecutionEventPoint(ExecutionEventPoint.FINANCIAL_RESULT_RECEIVED);
try {
myAppointmentService.handleFinancialResultReception(modelMapper.map(myPayload, AppointmentDTO.class));
}catch (ObjectOptimisticLockingFailureException e) {
log.warn(" ============================ Abording with ObjectOptimisticLocking Exception ============================ \n");
handleFailure();
}
}
public void handleFailure() {
log.warn(" ============================ Failure is being handled ============================ ");
}
2020-04-29 16:10:59.436 WARN 229560 --- [ntContainer#0-1] c.h.a.events.consumer.ConsumerService : ============================ Abording with ObjectOptimisticLocking Exception ============================
2020-04-29 16:10:59.436 WARN 229560 --- [ntContainer#0-1] c.h.a.events.consumer.ConsumerService : ============================ Failure is being handled ============================
2020-04-29 16:10:59.441 WARN 229560 --- [ntContainer#0-1] s.a.r.l.ConditionalRejectingErrorHandler : Execution of Rabbit message listener failed.
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'public void com.hospital.appointment.events.consumer.ConsumerService.handleQueueFinancialResultMessageReception(com.hospital.appointment.dto.AppointmentPayloadDTO)' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:228) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:148) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:133) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1579) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1498) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1486) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1477) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1421) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:963) [spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:913) [spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:81) [spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1284) [spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1190) [spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201]
Caused by: org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:752) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:631) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at com.hospital.appointment.events.consumer.ConsumerService$$EnhancerBySpringCGLIB$$5ebad6ff.handleQueueFinancialResultMessageReception(<generated>) ~[classes/:na]
at sun.reflect.GeneratedMethodAccessor103.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:171) ~[spring-messaging-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120) ~[spring-messaging-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:53) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:220) ~[spring-rabbit-2.2.5.RELEASE.jar:2.2.5.RELEASE]
Как вы можете видеть в журналах, исключение корректно перехватывается, но транзакция помечается как только откат, что на данный момент также нормально для меня .
Моя настоящая цель заключается в том, чтобы мое приложение автоматически получало, что Appointment
уже обновляется, и AppointmentServiceImpl
должен запустить другой процесс, вызываемый в методе AppointmentServiceImpl.handleFailure()
Но проблема заключается в том, что потребители сообщений продолжают получать сообщение после того, как оно откатывается несколько раз. В результате AppointmentServiceImpl.handleFailure()
и myAppointmentRepo.save
вызывают несколько раз. Итак, настоящая проблема здесь:
Проблема № 1: использование атрибута noRollbackFor
не работает: я что-то здесь не так делаю или есть обходной путь решения?
Решение № 2, если проблема № 1 не может быть решена, могу ли я сказать потребителю, что он не должен продолжать повторять?