JPA Lock - 첨부파일 트랜잭션 처리

wanuki·2023년 10월 23일
0

JPA Optimistic-Lock 사용 배경

  • 게시글을 수정할 때 첨부파일을 추가 혹은 삭제할 수 있다.
  • 게시글과 첨부파일의 카디널리티는 1:N 관계이다.
  • 게시글을 수정할 때 첨부파일도 함께 논리적인 트랜잭션 단위로 묶어서 처리해야 한다.
  • 게시글 수정은 자신이 작성한 게시글만 수정 가능하므로 충돌이 빈번하게 발생하지 않을 거라고 예상된다.

따라서 데이터베이스 락을 이용하는 비관적 락이 아닌 애플리케이션 레벨에서 버전 관리하는 낙관적 락을 사용했습니다.

JPA LockModeType

  • NONE: Lost Update 방지
  • OPTIMISTIC: REPETABLE READ 보장
  • OPTIMISTIC_FORCE_INCREMENT: 강제로 버전을 증가해서 논리적인 단위의 엔티티 묶음을 버전 관리할 수 있다.

게시물을 수정하는 데 단순히 첨부파일만 추가하거나 삭제하면 게시물의 버전은 증가하지 않는다. 하지만 해당 게시물은 물리적으로는 변경되지 않았지만, 논리적으로는 변경되었다.
OPTIMISTIC_FORCE_INCREMENT 옵션을 사용한다면 엔티티를 수정하지 않아도 트랜잭션을 커밋 할 때 UPDATE 쿼리를 사용해서 버전 정보를 강제로 증가시킨다. 엔티티 수정 시 버전 증가가 발생하고 커밋 때 버전 증가가 발생하므로 총 2번의 버전 증가가 나타날 수 있다.
따라서 OPTIMISTIC_FORCE_INCREMENT 사용하여 게시물의 버전 증가를 강제하여 논리적인 단위의 엔티티 묶음을 버전 관리할 수 있다.

업데이트 재시도

업데이트 시 버전 충돌이 발생하면 ObjectOptimisticLockingFailureException 예외가 발생한다.
예외 처리를 통해 수동으로 업데이트를 반복 수행하는 코드를 직접 추가했습니다.

AOP

재시도 로직을 Spring AOP를 사용해서 비즈니스 코드에서 분리했습니다.

@Order(1)
@Aspect
@Component
public class UpdateRetryAspect {

    @Around("@annotation(updateRetry)")
    public Object doUpdateRetry(ProceedingJoinPoint joinPoint,
                                UpdateRetry updateRetry) throws Throwable {
        int retryCount = updateRetry.value();
        ObjectOptimisticLockingFailureException exception = null;

        for (int cnt = 0; cnt < retryCount; cnt++) {
            try {
                return joinPoint.proceed();
            } catch (ObjectOptimisticLockingFailureException ex) {
                exception = ex;
                // 5번 재시도 이후 50millis 딜레이
                if (cnt > 4) Thread.sleep(50);
            }
        }

        throw new UpdateFailureException("잠시후 다시 시도해 주세요.", exception);
    }
}
※ 참고
이전 실행이 롤백 되고 난 뒤 재시도가 이뤄져야 한다.
따라서 재시도 어드바이스가 트랜잭션 어드바이스 보다 우선순위가 높아야 한다.
트랜잭션 어드바이스는 @Order 어노테이션이 없고 Odered 인터페이스를 구현하지 않은 경우 
AbstractPointcutAdvisor 클래스의 메서드 getOrder 반환값
Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)으로 정해진다.

reference

profile
하늘은 스스로 돕는 자를 돕는다

0개의 댓글