Spring data - enable optimistic locking
Optimistic Locking with Spring Data JPA is implemented by the JPA implementation used.
You are referring to P2 on page 93 of the JPA specs. The section starts with:
If transaction T1 calls
lock(entity, LockModeType.OPTIMISTIC)
on a versioned object, the entity manager must ensure that neither of the following phenomena can occur:
But your test doesn't create such a scenario. The method lock
never gets called. Therefore no relevant locking happens. Especially just loading an entity doesn't call lock
on it.
Things change when one modifies an object (Page 93 second but last paragraph of the spec):
If a versioned object is otherwise updated or removed, then the implementation must ensure that the requirements of
LockModeType.OPTIMISTIC_FORCE_INCREMENT
are met, even if no explicit call toEntityManager.lock
was made.
Note: you are spawning two threads using the same repository, which in turn will make them use the same EntityManager
. I doubt if this is supported by EntityManager
and also I'm not sure if you are actually getting two transactions at all this way, but that is a question for another day.
Related videos on Youtube
vratojr
Updated on July 13, 2022Comments
-
vratojr almost 2 years
Note: I DON'T NEED AN EXPLANATION CONCERNING THE OPTIMISTIC LOCKING. This question is about what seems to be a specific Spring Data behavior when using optimistic locking.
From the jpa specs whenever an entity has a
@Version
annotated field, optimistic locking should be enabled automatically on the entity.If I do this in a spring data test project using Repositories, the locking seems to not be activated. Infact no
OptimisticLockException
is thrown while doing a Non Repetable Read test (see P2 on page 93 of the JPA specs)However, from spring docs I see that if we annotate a single method with
@Lock(LockModeType.OPTIMISTIC)
then the underlying system correctly throws anOptimisticLockException
(that is then catch by spring and propagated up the stack in a slightly different form).Is this normal or did I miss something? Are we obliged to annotate all our methods (or to create a base repository implementation that takes the lock) to have optimistic behavior enabled with spring data?
I'm using spring data in the context of a spring boot project, version 1.4.5.
The test:
public class OptimisticLockExceptionTest { static class ReadWithSleepRunnable extends Thread { private OptimisticLockExceptionService service; private int id; UserRepository userRepository; public ReadWithSleepRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) { this.service = service; this.id = id; this.userRepository = userRepository; } @Override public void run() { this.service.readWithSleep(this.userRepository, this.id); } } static class ModifyRunnable extends Thread { private OptimisticLockExceptionService service; private int id; UserRepository userRepository; public ModifyRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) { this.service = service; this.id = id; this.userRepository = userRepository; } @Override public void run() { this.service.modifyUser(this.userRepository, this.id); } } @Inject private OptimisticLockExceptionService service; @Inject private UserRepository userRepository; private User u; @Test(expected = ObjectOptimisticLockingFailureException.class) public void thatOptimisticLockExceptionIsThrown() throws Exception { this.u = new User("email", "p"); this.u = this.userRepository.save(this.u); try { Thread t1 = new ReadWithSleepRunnable(this.service, this.u.getId(), this.userRepository); t1.start(); Thread.sleep(50);// To be sure the submitted thread starts assertTrue(t1.isAlive()); Thread t2 = new ModifyRunnable(this.service, this.u.getId(), this.userRepository); t2.start(); t2.join(); assertTrue(t1.isAlive()); t1.join(); } catch (Exception e) { e.printStackTrace(); } } }
The test service:
@Component public class OptimisticLockExceptionService { @Transactional public User readWithSleep(UserRepository userRepo, int id) { System.err.println("started read"); User op = userRepo.findOne(id); Thread.currentThread(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.err.println("read end"); return op; } @Transactional public User modifyUser(UserRepository userRepo, int id) { System.err.println("started modify"); User op = userRepo.findOne(id); op.setPassword("p2"); System.err.println("modify end"); return userRepo.save(op); } }
The repository:
@Repository public interface UserRepository extends CrudRepository<User, Integer> { }
-
Amr Alaa over 6 yearsI have working with this @Version annotation in my project and its really working through our rest services. What i a mentioned in my answer is really from a previous experience. Thanks