I need my app to have the following behavior:
Scenario 1
User A views an order.
User B views the same order.
- User A deletes the order.
- User B requests to update the order. This should fail.
Scenario 2
- User A views an order
- User B views the same order
- User A updates the order.
- User B request to delete the order. This should fail.
Using JPA (Hibernate via Spring Data JPA), I'm trying to use @Version
to implement this optimistic locking behavior:
@Entity
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Version
private Integer version;
// many other fields
On delete, the UI client gives the server a list of order ids along with the version number for each order id. This [post(Spring Data JPA: Delete Optimistic Locking semantics) mentions a standard solution:
if (entity.getVersion() != dto.getVersion()) {
throw new OptimisticLockException("...");
}
To use this, I have to
- lookup the entity from the database use the order id from the client
- compare the entity version to the DTO version from the client
- Perform the delete.
The problem is that at step 2 the entity and DTO version may be the same. But then at step 3, the version may be different. Is there a way to have hibernate perform the check and update as a single atomic action such as:
delete from [Order] where orderId = ? and version = ?
and throw StaleObjectStateException
if none deleted.
Update
I found two approaches that should work. Is there a problem with one of these two approaches? The second approach involves less trips to the database. The client will generally send only one order to delete at a time so performance should not be an issue here.
Approach 1
For each order to be deleted:
Order order = orderRepository.findById(
orderIdFromClient).orElseThrow(() ->
new OptimisticLockException());
if (!order.getVersion().equals(versionFromClient)) {
throw new OptimisticLockException();
}
// We now know the managed entity has the same version
// as the requested version. If some other transaction
// has changed the entity, Hibernate will rollback and
// throw OptimisticLockException.
orderRepository.delete(order);
Approach 2
Add an OrderRepository method:
int deleteByIdAndVersion(Long id, Integer version);
For each order to be deleted:
int x = orderRepository.deleteByIdAndVersion(orderIdFromClient, versionFromClient);
if (x==0) {
throw new OptimisticLockException();
}