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.
- How is version information affected by hibernate sessions? how does the version comparison actually happens?
- 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?
- 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();
}
}
}
}