1. 개요
이 빠른 사용방법(예제)에서는 사용자 지정 쿼리 메서드 및 미리 정의된 저장소 CRUD 메서드 에 대해 Spring Data JPA에서 트랜잭션 잠금을 활성화하는 방법에 대해 설명합니다.
또한 다양한 잠금 유형과 트랜잭션 잠금 시간 초과 설정에 대해 알아봅니다.
2. 잠금 유형
JPA에는 비관적 잠금과 낙관적 잠금의 두 가지 주요 잠금 유형이 정의되어 있습니다.
2.1. 비관적 잠금
트랜잭션에서 비관적 잠금 을 사용 하고 엔터티에 액세스하면 즉시 잠깁니다 . 트랜잭션은 트랜잭션을 커밋하거나 롤백하여 잠금을 해제합니다.
2.2. 낙관적 잠금
낙관적 잠금 에서 트랜잭션은 엔터티를 즉시 잠그지 않습니다. 대신 트랜잭션은 일반적으로 할당된 버전 번호와 함께 엔티티의 상태를 저장합니다.
다른 트랜잭션에서 엔터티의 상태를 업데이트하려고 하면 트랜잭션은 업데이트 중에 저장된 버전 번호를 기존 버전 번호와 비교합니다.
이때 버전 번호가 다르면 엔터티를 수정할 수 없음을 의미합니다. 활성 트랜잭션이 있는 경우 해당 트랜잭션이 롤백되고 기본 JPA 구현에서 OptimisticLockException 이 발생 합니다.
현재 개발 컨텍스트에 가장 적합한 것에 따라 타임스탬프, 해시 값 계산 또는 직렬화된 체크섬과 같은 다른 접근 방식을 사용할 수도 있습니다.
3. 쿼리 메서드에 대한 트랜잭션 잠금 활성화
엔티티에 대한 잠금을 획득하기 위해 필요한 잠금 모드 유형을 전달하여 잠금 어노테이션 으로 대상 쿼리 메소드에 어노테이션을 달 수 있습니다 .
잠금 모드 유형 은 엔터티를 잠그는 동안 지정되는 열거형 값입니다. 그런 다음 지정된 잠금 모드가 데이터베이스에 전파되어 엔터티 개체에 해당 잠금을 적용합니다.
Spring Data JPA 저장소의 사용자 정의 쿼리 메소드에 잠금을 지정하려면 @Lock 으로 메소드에 어노테이션을 달고 필요한 잠금 모드 유형을 지정할 수 있습니다.
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
@Query("SELECT c FROM Customer c WHERE c.orgId = ?1")
public List<Customer> fetchCustomersByOrgId(Long orgId);
findAll 또는 findById(id) 와 같은 사전 정의된 저장소 메서드에 잠금을 적용하려면 저장소 내에서 메서드를 선언하고 메서드에 Lock 어노테이션을 추가해야 합니다.
@Lock(LockModeType.PESSIMISTIC_READ)
public Optional<Customer> findById(Long customerId);
잠금이 명시적으로 활성화되고 활성 트랜잭션이 없는 경우 기본 JPA 구현은 TransactionRequiredException 을 발생 시킵니다.
잠금을 부여할 수 없고 잠금 충돌로 인해 트랜잭션 롤백이 발생하지 않으면 JPA는 LockTimeoutException 을 throw하지만 활성 트랜잭션을 롤백으로 표시하지 않습니다.
4. 트랜잭션 잠금 시간 초과 설정
비관적 잠금을 사용할 때 데이터베이스는 엔터티를 즉시 잠그려고 시도합니다. 기본 JPA 구현 은 잠금을 즉시 얻을 수 없는 경우 LockTimeoutException 을 발생시킵니다. 이러한 예외를 피하기 위해 잠금 시간 초과 값을 지정할 수 있습니다.
Spring Data JPA에서 잠금 시간 초과는 쿼리 메소드에 QueryHint 를 배치하여 QueryHints 어노테이션을 사용하여 지정할 수 있습니다.
@Lock(LockModeType.PESSIMISTIC_READ)
@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "3000")})
public Optional<Customer> findById(Long customerId);
이 ObjectDB 문서 에서 다양한 범위에서 잠금 시간 초과 힌트를 설정하는 방법에 대한 자세한 내용을 찾을 수 있습니다 .
5. 결론
이 기사에서는 다양한 유형의 트랜잭션 잠금 모드를 살펴보았습니다. 그런 다음 Spring Data JPA에서 트랜잭션 잠금을 활성화하는 방법을 배웠습니다. 잠금 시간 초과 설정에 대해서도 다루었습니다.
적절한 위치에 적절한 트랜잭션 잠금을 적용하면 대용량 동시 사용 애플리케이션에서 데이터 무결성을 유지하는 데 도움이 될 수 있습니다.
트랜잭션이 ACID 규칙을 엄격하게 준수해야 하는 경우 비관적 잠금을 사용해야 합니다. 여러 동시 읽기를 허용해야 하고 애플리케이션 컨텍스트 내에서 최종 일관성이 허용되는 경우 낙관적 잠금을 적용해야 합니다.
항상 그렇듯이 비관적 잠금과 낙관적 잠금 모두에 대한 샘플 코드는 Github 에서 찾을 수 있습니다 .