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.
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();
}
}
}
}