Я пытаюсь протестировать оптимистическую блокировку, используя аннотацию JPA @Version, которую я добавил к своему объекту сущности:
@Version
@Setter(AccessLevel.NONE)
@Column(name = "VERSION")
private long version;
Когда я запускаю 2 сервера одновременно, я получаю StaleObjectStateException:
Exception message is : Object of class [com.myPackage.WorkQueue] with identifier [9074]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.myPackage.WorkQueue#9074]
Я ожидал увидеть 1) появление OptimisticLockException и 2) откат @transaction в результате.
Пример использования следующий: записи вставляются в таблицу базы данных Oracle со статусом «NEW». Как только поток извлекает строку со статусом «NEW», он обновляет статус строки в таблице на «IN_PROGRESS». Мне нужно гарантировать, что любые транзакции, читающие одну и ту же строку одновременно, завершатся ошибкой / откатом, если другая транзакция успешно обновила эту строку.
Обслуживание:
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor=Exception.class, readOnly=false)
public WorkQueue retrieveWorkQueueItemByStatus(WorkQueueStatusEnum workQueueStatus) {
return workQueueRepository.retrieveWorkQueueItemByStatus(workQueueStatus);
}
Реализация:
@Override
public WorkQueue retrieveWorkQueueItemByStatus(WorkQueueStatusEnum workQueueStatus) {
log.debug("Start - Attempting to select a " + workQueueStatus + " workQueue item in retrieveWorkQueueItemByStatus()");
try {
String sql = "SELECT a FROM WorkQueue a WHERE workQueueStatus = :workQueueStatus ORDER BY idWorkQueue ASC";
TypedQuery<WorkQueue> query = em.createQuery(sql, WorkQueue.class).setParameter("workQueueStatus", workQueueStatus)
.setFirstResult(0).setMaxResults(1);
WorkQueue workQueue = (WorkQueue) query.getSingleResult();
if (workQueue != null) {
workQueue.setWorkQueueStatus(WorkQueueStatusEnum.IN_PROGRESS);
WorkQueue updatedWorkQueue = em.merge(workQueue);
log.debug("Finish - selected the following workQueue item "+ workQueue.getIdWorkQueue() + " with the Audit Event Key from retrieveWorkQueueItemByStatus() : " + updatedWorkQueue.getAuditEventKey());
return updatedWorkQueue;
}
} catch (IllegalArgumentException iae) {
log.error("An IllegalArgumentException occured in workQueueRepositoryImpl.retrieveWorkQueueItemByStatus() attempting to execute query : " + sql + ". Exception message is : " + iae.getMessage());
} catch(Exception ex) {
log.error("An Exception occured in workQueueRepositoryImpl.retrieveWorkQueueItemByStatus() executing query : " + sql + ". Exception message is : " + ex.getMessage());
}
log.debug("Finish - returning null from retrieveWorkQueueItemByStatus()");
return null;
}





См. этот вопрос о типе исключения.
Транзакция не откатывается автоматически. Этот вид исключения никогда не следует перехватывать и продолжать использовать сеанс Hibernate. Причина в том, что сеанс мог быть частично синхронизирован с базой данных, поэтому состояние базы данных могло быть несогласованным. Так что пусть исключение будет выброшено за пределами транзакции, где оно откатывается, и начнется с нового сеанса.
Стефан, я удалил операторы try catch из приведенной выше реализации и добавил throws RuntimeException в сигнатуру метода retrieveWorkQueueItemByStatus(). Правильно ли я предполагаю, что транзакция откатится, когда Hibernate выбрасывает StaleObjectStateException?
Я бы проверил код Hibernate, когда для вас важно, чтобы транзакция откатывалась в тот момент, когда генерируется исключение. Обычно исключение выбрасывается из некоторого блока попытки (с ресурсами), и транзакция и сеанс закрываются. Это тот момент, когда он хотя бы откатывается.
Вы говорите, что приведенный выше синтаксис специфичен для спящего режима, а не для JPA, поэтому я не получаю правильный тип исключения? Почему бы автоматически не откатить транзакцию?