Optimistic locking in Spring boot REST application

148 Views Asked by At

I read thru lots of documentation on optimistic locking in Spring Boot but I couldn't get it to work for my use case. I have 2 users who pull up the same version of the record on the web page, user1 updates the record and then user2 updates it. So user2's changes have overwritten the changes made by user1. The documentation talks about using a @Version annotation but this doesn't work for my use case since Spring JPA does not have the old version number.

I solved this by sending the last updated datetime to the client on the GET request. The client uses this as a hidden field on the form and sends it back to the server on the PUT request. I now do a findById on the entity and compare the last updated datetime sent by the client with the value returned by the findById method. If they don't match, I throw an exception.

I'm not using the @Version annotation at all since it does not help me. Also, I cannot change the Database model. We have a last updated datetime field in all the DB tables. I'm using this field to track the version.

I also locked the record exclusively just before the update by using LockModeType.PESSIMISTIC_WRITE. I was not able to pass the LockModeType parameter to the standard findBy methods in a JPA repository. I need to pass it a parameter since I need the lock only before an update. I tried to follow the steps in the link below and create a custom repository implementation for a locked method, but I kept getting an error that the lockById method is not recognized. https://vladmihalcea.com/spring-data-jpa-locking/

Would appreciate the following.

a. If any of you got optimistic locking to work for a use case that is exactly like mine, did you use hidden fields like I did? If you did it a different way, please let me know.

b. If you know how to pass the LockModeType parameter to a standard JPA repository findBy method, please let me know. If it cannot be done and I have to use EntityManager, then I'll try to find a way to do that.

1

There are 1 best solutions below

0
K.Nicholas On

Sure, it's easy as Spring supports Timestamp as a version field. Annotation Interface Version.

@Entity
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DatabaseRecord {
    @Id
    private Long id;
    @Version
    private Timestamp lastUpdated;
    private String somethingToUpdate;
}

and then just catch the exception and retry.

Optional<DatabaseRecord> databaseRecord1 = playService.query();
Optional<DatabaseRecord> databaseRecord2 = playService.query();
playService.update(databaseRecord1.get(), "first update");
try {
    playService.update(databaseRecord2.get(), "second update");
} catch (ObjectOptimisticLockingFailureException lockingFailureException ) {
    databaseRecord2 = playService.query();
    playService.update(databaseRecord2.get(), "second update");
}
System.out.println(playService.query().get());

gives me

Hibernate: select d1_0.id,d1_0.last_updated,d1_0.something_to_update from database_record d1_0 where d1_0.id=?
Hibernate: select d1_0.id,d1_0.last_updated,d1_0.something_to_update from database_record d1_0 where d1_0.id=?
Hibernate: select d1_0.id,d1_0.last_updated,d1_0.something_to_update from database_record d1_0 where d1_0.id=?
Hibernate: update database_record set last_updated=?,something_to_update=? where id=? and last_updated=?
Hibernate: select d1_0.id,d1_0.last_updated,d1_0.something_to_update from database_record d1_0 where d1_0.id=?
Hibernate: select d1_0.id,d1_0.last_updated,d1_0.something_to_update from database_record d1_0 where d1_0.id=?
Hibernate: select d1_0.id,d1_0.last_updated,d1_0.something_to_update from database_record d1_0 where d1_0.id=?
Hibernate: update database_record set last_updated=?,something_to_update=? where id=? and last_updated=?
Hibernate: select d1_0.id,d1_0.last_updated,d1_0.something_to_update from database_record d1_0 where d1_0.id=?
DatabaseRecord(id=1, lastUpdated=2024-01-01 14:27:02.036552, somethingToUpdate=second update)