@Slf4j public class MyTest { @Autowired private TestService service; @Test @Rollback(false) public void lockTest() throws Exception { val targetId = 100L; Thread thread1 = new Thread(() -> { updateWorkerValue(targetId, "worker1");}); Thread thread2 = new Thread(() -> { updateWorkerValue(targetId, "worker2");}); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } private void updateWorkerValue(Long id, String worker) { log.info("+++ {}: STARTED", worker); try { service.getLock(id); } catch (Exception e) { log.info("+++ {}: FAIL TO LOCK", worker); return; } log.info("+++ {}: GOT LOCK", worker); var result = ERROR; try { service.updateWorkerValue(id, worker); log.info("+++ VALUE UPDATED: {}", worker); result = DONE; } finally { service.updateLockedJobStatusTo(id, result); } log.info("+++ DONE: " + worker); } }
@Service public class TestService { @Autowired private JobRepository repo; @Transactional public void getLock(Long id) { val affected = repo.updateStatus(id, statuses(WAIT), newStatus(LOCK)); if (affected != 1) throw new RuntimeException("fail to lock: id=" + id); } @Transactional public void updateWorkerValue(Long id, String worker) { // TODO biz repo.updateValue(id, worker, status(LOCK)); } @Transactional public void updateLockedJobStatusTo(Long id, JobStatus newStatus) { val affected = repo.updateStatus(id, statuses(LOCK), newStatus); if (affected != 1) throw new RuntimeException("error while updating status. id:" + id + ", status:" + newStatus); } }
@Repository public interface JobRepository { @Modifying @Query("update JobItem i set i.status=:newStatus, i.modifiedAt=now() where i.id=:id and i.status in :statuses") int updateStatus( @Param("id") Long id, @Param("statuses") List<JobStatus> statuses, @Param("newStatus") JobStatus newStatus); @Modifying @Query("update JobItem i set i.stringUnitId=:value, i.modifiedAt=now() where i.id=:id and i.status=:status") int updateValue( @Param("id") Long id, @Param("value") String value, @Param("status") JobStatus status); }