问题
I'm trying to test optimistic locking using JPA's @Version
annotation that I've added to my entity object:
@Version
@Setter(AccessLevel.NONE)
@Column(name = "VERSION")
private long version;
When I run 2 servers concurrently, I receive a 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]
I was expecting to see 1) an OptimisticLockException
to occur and 2) the @transaction
to get rolled back as a result.
The use case is as follows: Entries get inserted into an Oracle Database Table with a status of 'NEW'. Once a thread retrieves the row with the status = 'NEW', it updates the row's status on the Table to 'IN_PROGRESS'. I need to ensure any transactions reading the same row at the same time fail/rollback if another transaction has successfully updated that row.
The Service:
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor=Exception.class, readOnly=false)
public WorkQueue retrieveWorkQueueItemByStatus(WorkQueueStatusEnum workQueueStatus) {
return workQueueRepository.retrieveWorkQueueItemByStatus(workQueueStatus);
}
The Implementation:
@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;
}
回答1:
See this question about the exception type.
The transaction is not rolled back automatically. This kind of exception should never be caught and continued using the Hibernate session. The reason is that the session could have been been partially synchronized to the database, therefor the database state could be inconsistent. So let the exception be thrown outside of the transaction, where it is rolled back, and start over with a new session.
来源:https://stackoverflow.com/questions/50001990/hibernate-staleobjectstateexception-issue-calling-merge