트랜잭션과 락(2) - 락 기본

2024. 2. 15. 21:14spring/JPA

낙관적 락과 비관적 락 들어가기에 앞서...

JPA의 영속성 컨텍스트(1차 캐시)를 적절히 활용하면 데이터베이스 트랜잭션이 READ COMMITTED 격리 ㅅ준이어도 애플리케이션 레벨에서 반복 가능한 읽기가 가능하다.

 => 다시 조회할 때 1차 캐시에 저장된 엔티티를 조회하기 때문

엔티티가 아닌 스칼라 값을 직접 조회하면 영속성 컨텍스트의 관리를 받지 못하므로 반복 가능한 읽기를 할 수 없다.

 

 

JPA는 데이터베이스 트랜잭션 격리 수준을 READ COMMITTED 정도로 가정한다. 만약 중요한 비즈니스 로직에 더 높은 격리 수준이 필요하다면 낙관적 락과 비간적 락 중 하나를 사용하면 된다.

 

 

 낙관적 락은 이름 그대로 트랜잭션 충돌이 발생하지 않는다고 낙관적으로 가정하는 방법이다. 이것은 데이터베이스가 제공하는 락 기능을 사용하는 것이 아니라 JPA가 제공하는 버전 관리 기능을 사용한다. 낙관적 락은 트랜잭션 커밋 전까지 트랜잭션의 충돌을 알 수 없다는 특징이 있다.

 => 플러시 이후 SQL을 보내기 때문

 

 

 비관적 락은 이름 그대로 트랜잭션의 충돌이 발생한다고 가정하고 우선 락을 걸고 보는 방법이다. 이것은 데이터베이스가 제공하는 락 기능을 사용한다. 대표적으로 SELECT FOR UPDATE 구문이 있다.

 

여기에 추가로 데이터베이스 트랜잭션 범위를 넘어서는 문제도 있다.

 

 예를 들어 사용자 A와 B가 동시에 제목이 같은 공지사항을 수정한다고 가정해보자. 둘이 동시에 수정 화면을 열어서 내용을 수정하는 중에 사용자 A가 먼저 수정완료 버튼을 누르고 잠시 후에 사용자 B가 수정완료 버튼을 눌렀다. 결과적으로 먼저 완료한 사용자 A의 수정사항은 사라지고 나중에 완료한 사용자 B의 수정사항만 남게 된다. 이것을 두번의 갱신 분실 문제라 한다.

 두번의 갱신 분실 문제는 데이터베이스 트랜잭션의 범위를 넘어선다 이때는 3가지 선택 방법이 있다.

 

1. 마지막 커밋만 인정하기

=> 사용자 A의 커밋은 무시하고 마지막에 커밋한 사용자B의 내용만 인정한다.

 

2. 최초 커밋만 인정하기

=> 사용자 A가 이미 수정을 완료했으므로 사용자 B가 수정을 완료할 때 오류가 발생한다.

 

3. 충돌하는 갱신 내용 병합하기

=> 사용자 A와 B의 수정사항을 병합한다.

 

 

 

@Version

낙관적 락과 비관적 락을 설명하기 전에 먼저 @Version을 알아보자. JPA가 제공하는 낙관적 락을 사용하려면 @Version 어노테이션을 사용해서 버전 관리 기능을 추가해야 한다.

 

@Version 적용 가능 타입은 다음과 같다.

Long, Integer, Short, Timestamp

 

@Entity
@DiscriminatorValue("I")
public class Book extends Item {

    private String autor;
    private String isbn;
    
    @Version
    private Integer version;
}

 

버전 관리 기능을 적용하려면 엔티티에 버전 관리용 필드를 하나 추가하고 @Version을 붙이면 된다. 이제부터 엔티티를 수정할 때마다 버전이 하나씩 자동으로 증가한다. 그리고 엔티티를 수정할 때 조회 시점의 버전과 수정 시점의 버전이 다르면 예외가 발생한다.

 예를 들어 트랜잭션 1이 조회한 엔티티를 수정하고 있는데 트랜잭션 2에서 같은 엔티티를 수정하고 커밋해서 버전이 증가해버리면 트랜잭션 1이 커밋할 때 버전 정보가 다르므로 예외가 발생한다.

 

따라서 버전 정보를 사용하면 최초 커밋만 인정하기가 적용된다.