Home > database >  Optimistic locking across 2 different Hibernate Sessions
Optimistic locking across 2 different Hibernate Sessions

Time:04-21

I have written an update method inside the service layer like this, I am using Spring-data JpaRepository for all the CRUD operations.

    public Note updateNote(Note note, Long id) {
        Note current = repository.getOne(id);

        current.setTitle(note.getTitle());
        current.setDetail(note.getDetail());

        return repository.save(current);
    }

I want to do an optimistic lock over this operation, so I have added a version field in the Note entity.

  1. How is version information affected by hibernate sessions? how does the version comparison actually happens?
  2. I tried simulation this scenario with some tests using multiple threads, but couldn't do so and couldn't verify if this works or not. is this method correct or wrong? Will the optimistic locking work if the object is in a detached state? or is there some rule which dictates when hibernate will check for the version matching between the old and new entity?
  3. In addition, do I need to explicitly annotate it with @Lock annotation? or it will pick up OPTIMISTIC_READ by default?

CodePudding user response:

2: No you don't need any @Lock for optimistic locking, using @Version we achieve optimistic locking.

1: Now you can test optimistic locking scenario using Threading, below I have write some sample test code, you can update this based on your requirement.

let suppose I have Note entity :

@Entity
@Data
@ToString
public class Note {
    @Id
    @GeneratedValue
    private Long id;

    @Version
    private int version;

    private String name;
}

Also I have Note Reposotory for performing DB oprration

@Repository

public interface NoteRepo extends JpaRepository<Note, Long> {
}

Now I have a Note Service

@Service
@AllArgsConstructor
public class NoteService {

    private final NoteRepo noteRepo;

    @Transactional
    public Note create() {
        Note note = new Note();
        note.setName("Test Sample");
        note = noteRepo.save(note);
        System.out.println(note);
        return note;
    }

    @Transactional
    public void update(Long id, String name) {
       Note note =  noteRepo.getById(id);
       note.setName(name );
       note = noteRepo.save(note);
    }
}

Now time to Test Optimistic Locking with Integration Test.

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes =Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestServiceTest {
 @Autowired
    private NoteService noteService;

    @Test
    void assertObjectOptimisticLockingFailure() throws InterruptedException {
        // Create a note, so after this we can perform updation
        Note note = noteService.create();

        //CountDownLatch required for wait test to complete
        CountDownLatch countDownLatch = new CountDownLatch(2);

       // Create 2 NoteThread to perform updation for exising note entity
        NoteThread xNote = new NoteThread("X", note.getId(), countDownLatch);
        NoteThread yNote = new NoteThread("Y", note.getId(), countDownLatch);

        xNote.start();
        yNote.start();

        countDownLatch.await();

        // Validate one of thread have ObjectOptimisticLockingFailureException

        assertTrue(xNote.hasObjectOptimisticLockingFailure() || yNote.hasObjectOptimisticLockingFailure());

    }

    class NoteThread extends Thread {
        private final String name;
        private final Long id;
        private final CountDownLatch countDownLatch;
        private Class exceptionClass;
        
        // verify exception
        boolean hasObjectOptimisticLockingFailure() {
            return this.exceptionClass == ObjectOptimisticLockingFailureException.class;
        }

       // Custome Thread class for performing note update and verify lock exception
        public NoteThread(String name, Long id, CountDownLatch countDownLatch) {
            this.name = name;
            this.id = id;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                noteService.update(id, name);
            } catch (Exception e) {
                exceptionClass = e.getClass();
            }finally {
                countDownLatch.countDown();
            }
        }
    }
}
  • Related