Чтобы добиться этого, мы добавили Оптимистическая блокировка JPA к существующему приложению.
Пример:
Код: Выделить всё
@Entity
public class Payment {
@Id
protected Long id;
@Version
protected Long version;
protected String lastStatus; //the one I want to protect from concurrent processes
@ManyToOne
protected Channel inputChannel;
//Other columns
}
@Entity
public class Channel {
@Id
protected Long id;
// Not changing frequently (ever...), no version attribute here
}
Однако у меня есть метод обновления общего назначения, который может обновлять практически каждое поле в сущности. , в частности отношение к Каналу. Из-за функциональных ограничений не предполагается, что возникнут проблемы с условиями гонки.
Код: Выделить всё
@Transactional
public class PaymentManager {
public void update(PaymentDto dto){
var payment = repository.findById(dto.getId()).orElseThrow();
// perform all updates
// this INCLUDES changing the channel
if (dto.getInputChannel() != null && dto.getInputChannel().getId() != null) {
payment.setInputChannel(channelRepository.getReferenceById(dto.getInputChannel().getId()));
}
repository.save(payment);
}
@Retryable
public boolean upgradeStatus(Long paymentId, Status expectedStatus, Status newStatus) {
var payment = repository.findById(dto.getId()).orElseThrow();
if (!expectedStatus.name().equals(payment.getStatus()) {
return false;
}
payment.setStatus(newStatus.name());
repository.save(payment);
return true;
}
}
public class PaymentRepository extends JpaRepository.... {
@Lock(LockMode.OPTIMISTIC)
Optional findById(Long id);
}
Но теперь проблема в том, что, судя по всему, Hibernate пытается выполнить проверку оптимистической блокировки и для объекта Channel
Код: Выделить всё
org.springframework.messaging.MessageHandlingException: error occurred during processing message in 'MethodInvokingMessageProcessor' [org.springframework.integration.handler.MethodInvokingMessageProcessor@6e9bf992]
at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:191)
at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:117)
at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:93)
[omitted]
org.springframework.messaging.MessageHandlingException: error occurred during processing message in 'MethodInvokingMessageProcessor' [org.springframework.integration.handler.MethodInvokingMessageProcessor@6e9bf992]
at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:191)
at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:117)
at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:93)
[omitted]
... 401 common frames omitted
Caused by: org.hibernate.HibernateException: Unable to perform beforeTransactionCompletion callback: Cannot invoke "Object.equals(Object)" because the return value of "org.hibernate.engine.spi.EntityEntry.getVersion()" is null
at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:1022)
at org.hibernate.engine.spi.ActionQueue.beforeTransactionCompletion(ActionQueue.java:546)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1982)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562)
... 429 common frames omitted
Caused by: java.lang.NullPointerException: Cannot invoke "Object.equals(Object)" because the return value of "org.hibernate.engine.spi.EntityEntry.getVersion()" is null
at org.hibernate.action.internal.EntityVerifyVersionProcess.doBeforeTransactionCompletion(EntityVerifyVersionProcess.java:41)
at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:1016)
... 436 common frames omitted
Hibernate, похоже, не догадывается (должен ли?), что меня не интересует отслеживание версии Channel, потому что я я не вношу никаких изменений в свое приложение. Канал — это практически неизменяемая сущность. Главным образом потому, что он может измениться, но метод обновления никогда не меняет содержимое канала.
Итак, мои вопросы:
- Это нормальное поведение в JPA? Вам также нужно добавить блокировку ко всем зависимым объектам в отношениях дочерний->родительский?
- Существует ли стандартный способ сообщить модели JPA, что ссылка на канал не подлежит для блокировки при загрузке объекта «Платеж»?
- Или это может быть ошибка в Hibernate, который не обнаруживает, что канал не блокируется, и все еще пытается выполнить проверку?
После экспериментов выяснилось, что это НЕ связано с обновлением канала. Само присутствие объекта Channel в Платеже вызывает проблему
Подробнее здесь: https://stackoverflow.com/questions/793 ... no-version