낙관적 락 (Optimisstic Lock)


낙관적 락 (Optimisstic Lock) 이란?

  • 락이 생길 경우가 없다고 낙관할때
  • 어플리케이션 레벨에서 제공하는 Lock 기능을 사용
  • JPA가 제공하는 엔티티의 버전 관리 기능을 사용
  • 트랜잭션 커밋 전에는 트랜잭션 충돌을 알 수 없음

JPA가 제공하는 낙관적 락 옵션(LockModeType)

  • NONE :  엔티티를 수정하는 시점에 버전이 증가함
    (엔티티에 @Version이 있으면 기본으로 적용되는 락 옵션)
  • OPTIMISTIC :  엔티티를 조회하는 시점에 버전이 증가함
    (Dierty Read 와 Non-Repeatable Read를 방지)
  • OPTIMISTIC_FORCE_INCREMENT : 논리적으로 변경되었을 경우도 버전이 증가함
    (자식 엔티티만 변경되도 부모 엔티티의 버젼이 강제 증가)

* Non-Repeatable Read 이란 한 트랜잭션내에서 같은 쿼리를 두 번 수행했을 때, 결과가 다르게 나타나는 현상을 의미함

 

낙관적 락 옵션(LockModeType)  적용 예제

@Repository
public interface UserMasterJpa extends JpaRepository<UserMasterEntity, String> {
    /**
     * JPA가 제공하는 낙관적 락 옵션(LockModeType)
     * LockModeType.NONE <-- Entity 에 @Version 있다면 Default 임
     * LockModeType.OPTIMISTIC
     * LockModeType.OPTIMISTIC_FORCE_INCREMENT
     */
    @Lock(LockModeType.OPTIMISTIC)
    Optional<UserMasterEntity> findByUserId(Long id);
}

 

버젼 명시 예제

public class UserMasterEntity {

    @Id
    @Column(name = "USER_ID", nullable = false, length = 5)
    private String userId;
    
    @Version
    @Column(name = "DATA_VERSION", nullable = true, precision = 0)
    private Integer dataVersion;

}

 

Lock 을 사용함에 따라 발생할 수 있는 예외

  • ObjectOptimisticLockingFailureException
    (트랜잭션 커밋 시점에 버전이 같지 않으면 발생하는 예외)

사용시 주의사항

  • DB-Lock 을 사용하지 않지만 Dead-Lock이 발생할 수 있음
    (x-Lock이 사용될 경우)
  • 롤백(Rolback) 이슈(어플리케이션 단에서 롤백을 수행해야 함)

* Update 쿼리에 사용되는 모든 레코드에 exclusive lock(x-Lock)을 설정한다고 한다

 

락이 생길 경우가 없다고 낙관할때 사용하는게 좋아보임
(충돌이 예상되거나 충돌이 발생했을 때 비용이 많이 들것이라고 판단되는 곳에서는 사용하지 않는 것이 좋음)

비관적 락 (Pessimistic Lock)


비관적 락 (Pessimistic Lock) 이란?

  • DB에서 제공하는 Lock 기능을 사용
  • 엔티티가 아닌 스칼라 타입을 조회할 때도 사용가능
  • Lock을 획득할 때까지 트랜잭션은 대기 - Lock Timeout 설정가능

JPA가 제공하는 비관적 락 옵션(LockModeType)

  • PESSIMISTIC_WRITE :  베타락, 쓰기/읽기 Lock (Non-Repeatable Read를 방지)
  • PESSIMISTIC_READ :  공유락, 읽기 Lock 
  • PESSIMISTIC_FORCE_INCREMENT : 베타락, 쓰기/읽기 Lock, 낙관적락처럼 버저닝따라서 버전에 대한 컬럼이 필요
    (하이버네이트의 경우 nowait 를 지원하는 데이터베이스에 대해서 FOR UPDATE NOWAIT 옵션을 적용하고, 그렇지 않다면 FOR UPDATE 를 적용한다)

비관적 락 옵션(LockModeType)  적용 예제

@Repository
public interface UserMasterJpa extends JpaRepository<UserMasterEntity, String> {
    /**
     * JPA가 제공하는 비관적 락 옵션(LockModeType)
     * LockModeType.PESSIMISTIC_READ
     * LockModeType.PESSIMISTIC_WRITE
     * LockModeType.PESSIMISTIC_FORCE_INCREMENT
     */
    @Lock(LockModeType.PESSIMISTIC_READ)
    Optional<UserMasterEntity> findByUserId(Long id);
}

 

Lock Timeout 적용 예제 - DBMS에서 제공안할 수 도 있음

@Repository
public interface UserMasterJpa extends JpaRepository<UserMasterEntity, String> {
    /**
     * Lock Timeout은 락을 잡고 있는 최대 시간을 설정
     */
    @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value ="10000")})
    @Lock(LockModeType.PESSIMISTIC_READ)
    Optional<UserMasterEntity> findByUserId(Long id);
}

 

Lock Scope 적용 예제 - DBMS에서 제공안할 수 도 있음

@Repository
public interface UserMasterJpa extends JpaRepository<UserMasterEntity, String> {
    /**
     * 락 범위(Lock Scope)를 지정
     * NORMAL : 엔터티 자체를 잠급니다. 결합된 상속과 함께 사용하면 조상도 잠김
     * EXTENDED : NORMAL 과 동일한 기능을 포함하며 조인 테이블에서 관련 엔터티를 차단할 수 있습니다.
     */
    @QueryHints({@QueryHint(name = "javax.persistence.lock.scope", value = "EXTENDED")})
    @Lock(LockModeType.PESSIMISTIC_READ)
    Optional<UserMasterEntity> findByUserId(Long id);
}

 

Lock 을 사용함에 따라 발생할 수 있는 예외

  • PessimisticLockException
    (한 번에 하나의 Lock만 얻을 수 있으며, Lock을 가져오는데 실패하면 발생하는 예외)
  • LockTimeoutException
    (락을 기다리다가 설정해놓은 wait time을 지났을 경우 발생하는 예외)
  • PersistanceException
    (영속성 문제가 발생했을 때 발생하는 예외)

사용시 주의사항

  • @Lock 어노테이션이 붙은 메서드 호출은 @Transaction 내부에서 동작함
  • 만약 @Transaction 어노테이션의 영역(Scope) 밖에서 @Lock 어노테이션이 붙은 메서드를 호출한다면 아래와 같은 에러발생
    (javax.persistence.TransactionRequiredException: no transaction is in progress)

 

+ Recent posts