Spring data - enable optimistic locking

17,214

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 to EntityManager.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.

Share:
17,214

Related videos on Youtube

vratojr
Author by

vratojr

Updated on July 13, 2022

Comments

  • vratojr
    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 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> {
    }
    
  • Amr Alaa
    Amr Alaa over 6 years
    I 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