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 an OptimisticLockException
(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> {
}