Проблема обработки ObjectOptimisticLockingFailureException в проекте с использованием RabbitMQ - PullRequest
0 голосов
/ 29 апреля 2020

Мне трудно справиться с 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 не может быть решена, могу ли я сказать потребителю, что он не должен продолжать повторять?

...