public class NoLockWorker implements Runnable{
private CountDownLatch countDownLatch;
public NoLockWorker(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
postService.plusHitById(BASE_ID);
countDownLatch.countDown();
}
}
------------------------------------------------------------------
public void plusHitById(Long id){
context.transaction(configuration ->{
final Post post = DSL.using(configuration)
.selectFrom(POST)
.where(POST.ID.eq(id))
.fetchOneInto(Post.class);
final Long hit = post.getHit();
DSL.using(configuration)
.update(POST)
.set(POST.HIT, hit + 1L)
.where(POST.ID.eq(id))
.execute();
});
}
Database (Post 테이블)
Gradle
=> jOOQ 설정 시 Gradle에서 사용할 version 컬럼을 지정할 수 있습니다.
DslContext 세팅시 withExecuteWithOptimisticLocking(true) 옵션을 추가한다.
또한, OptimisticLock이 충돌 때문에 예외를 던지면 커넥션을 계속 물고 있는 상황이 생기는데, try with resources를 사용하여 충돌 시 바로 커넥션을 종료시킨다.
public void plusHitOptimisticById(Long id){
try(Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD)){
DSL.using(connection, SQLDialect.MYSQL, new Settings().withExecuteWithOptimisticLocking(true))
.transaction(configuration -> {
PostRecord postRecord = DSL.using(configuration)
.fetchOne(POST, POST.ID.eq(id));
postRecord.setHit(postRecord.getHit() + 1L);
postRecord.store();
});
} catch (SQLException e) {
log.info("error code : {}, error : {}", e.getErrorCode(), e);
}
}
forUpdate는 다른 트랜잭션의 CRUD 모두 허용하지 않는 특징이 있다.
앞으로 동시성을 제어해야하 하는 상황에서, 어플리케이션 레벨에서 제어를 할지 데이터베이스 레벨에서 제어를 할지 조금은 더 고민해볼 수 있는 시간이 될 것 같습니다.
데이터베이스 레벨에서 제어한다고 하면 어떤 Lock이 더 비즈니스에 맞을 지 생각하고 선택할 수 있고, Optimistic Lock을 사용하면 어떤 식으로 구성해야할지 고민해볼 것 같습니다.
이번에 개인적으로 공부하면서 저희 파트에도 공유하고 회사 프로젝트에 적용했던 기회였기 때문에 더 재밌게 진행할 수 있었던 것 같습니다.
https://www.jooq.org/doc/latest/manual/sql-execution/crud-with-updatablerecords/optimistic-locking/
좋은 글 감사합니다. jooq 관련해서 조금 더 써주시면 감사할 것 같아요!