본문 바로가기

SpringFramework/JPA

JPA - 트랜잭션과 락 (+격리수준)

트랜잭션과 격리레벨

트랜잭션은 아래 4가지 성질을 보장해야 한다.

- 원자성(Atomicity)

: 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하던지, 실패하던지 해야한다. 부분적 성공, 실패는 없다.

 

- 일관성(Consistency)

: 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다. 만약 게시판 제목의 컬럼타입이 VARCHAR(255) 라면, 이 조건에 맞아야한다.

 

- 격리성(Isolation)

: 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다. 동시에 같은 데이터가 수정되면 안된다. 트랜잭션끼리 서로 간섭할 수 없다.

 

- 지속성(Durability)

: 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다. 중간에 시스템에 문제가 발생하더라도 데이터베이스 로그 등을 사용하여 성공한 트랜잭션 내용을 복구해야 한다.

 

트랜잭션은 원자성, 일관성, 지속성을 보장하는데에는 큰 이슈가 없지만, 격리성에서 성능상의 이슈가 존재한다. 격리성을 완벽히 보장하려면, 트랜잭션을 거의 차례대로 실행해야할 것이다. 그러면 동시처리 성능이 매우 나빠질 것이며 이런 문제로 인하여 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어서 정의하였다.

 

격리수준 DIRTY READ NON-REPEATABLE READ PHANTOM READ
READ UNCOMMITTED
(커밋되지 않은 읽기)
O O O
READ COMMITTED
(커밋된 읽기)
  O O
REPEATABLE READ
(반복 가능한 읽기)
    O
SERIALIZABLE
(직렬화 가능)
     

- DIRTY READ : 트랜잭션1이 커밋하지 않아도 트랜잭션2가 수정 중인 데이터를 조회할 수 있다.

(만약 트랜잭션1이 롤백한다면.. 심각한 데이터 정합성 문제가 발생)

 

- NON-REPEATABLE READ : 반복해서 같은 데이터를 읽을 수 없는 상태. READ COMMITTED 수준까지는 반복읽기가 가능하다.

(트랜잭션 1이 회원A를 조회중인데 갑자기 트랜잭션 2가 회원A를 수정하고 커밋하면 트랜잭션1이 다시 회원A를 조회했을때 수정된 데이터가 조회됨)

 

- PHANTOM READ : 반복조회시 결과 집합이 달라진다.

(트랜잭션1이 10살 이하의 회원을 조회했는데 트랜잭션 2가 5살 회원을 추가하고 커밋하면 트랜잭션1이 다시 10살 이하의 회원을 조회 했을 때 회원 하나가 추가되는 상태로 조회됨)

 

데이터베이스들은 보통 READ COMMITTED 격리 수준을 기본으로 사용한다.

낙관적 락과 비관적 락

= 낙관적 락

: 트랜잭션 대부분은 충돌이 발생하지 않는다고 가정하는 방법.

데이터베이스가 제공하는 락 기능을 사용하지 않고, JPA가 제공하는 버전 관리 기능을 사용한다. 트랜잭션을 커밋하기 전까지는 트랜잭션의 충돌을 알 수 없다.

 

- @Version

낙관적 락을 사용하기 위해 사용하는 JPA가 제공하는 버전 관리 기능

@Version
private Integer version;

/* Version어노테이션을 적용할 때
   사용가능한 데이터 타입은 Long(long), Integer(int), Short(short), Timestamp 를 지원한다.
 */

=> 엔티티를 수정할 때 마다 버전이 하나씩 자동으로 증가. 조회 시점의 version과 수정 시점의 version이 다르면 예외가 발생.

    - 락 옵션 없이 @Version 어노테이션만 있어도 낙관적 락이 적용된다.

    - 버전정보를 사용하면 최초 커밋만 인정하기가 적용된다. (이후에 시도하는 UPDATE는 버전이 이미 증가한 것으로 판단하여 예외를 발생시킨다)

    javax.persistence.OptimisticLockException (JPA예외)

    org.hibernate.StaleObjectStateException (하이버네이트 예외)

    org.springframework.orm.ObjectOptimisticLockingFailureException (스프링 예외 추상화)

    - @Version으로 추가한 버전 관리 필드는 JPA가 직접 관리하므로 개발자가 임의로 수정하면 안된다. 강제로 증가가 필요시, 특별한 락옵션을 선택한다.

 

- 락 사용하기

1] EntityManager.lock(), EntityManager.find(), EntityManager.refresh()

2] Query.setLockMode()

3] @NamedQuery

=> 해당 위치에 락을 적용할 수 있다.

 

= 비관적 락

: 트랜잭션의 충돌이 발생한다고 가정하고 우선 락을 걸고 보는 방법. 데이터베이스가 제공하는 락 기능을 사용한다.

- 엔티티가 아닌 스칼라 타입을 조회할 때도 사용할 수 있다.

- 데이터를 수정하는 즉시 트랜잭션 충돌을 감지할 수 있다.

- 발생 예외

    javax.persistence.PessimisticLockException (JPA예외)

    javax.persistence.LockTimeoutException (락 획득 타임아웃)

    org.springframework.dao.PessimisticLockingFailureException (스프링 예외 추상화)

 

LockModeType 속성
락 모드 타입 설명
낙관적 락 OPTIMISTIC 낙관적 락을 사용
- 엔티티를 조회만 해도 버전 체크를 진행
- 조회 시점부터 트랜잭션이 끝날 때까지 조회한 엔티티가 변경되지 않음을 보장
- DIRTY READ, NON-REPEATABLE READ를 방지
낙관적 락 OPTIMISTIC_FORCE_INCREMENT 낙관적 락 + 버전정보를 강제로 증가
- 엔티티를 수정하지 않아도 트랜잭션을 커밋할 때 UPDATE쿼리를 사용해서 버전 정보를 강제로 증가시킴
- 예를들어, 게시판 내용은 변경이 안되었지만 첨부파일만 추가되었을 경우에도 강제로 버전 정보를 증가시킨다.
비관적 락 PESSIMISTIC_READ 비관적 락, 읽기 락을 사용
비관적 락 PESSIMISTIC_WRITE 비관적 락, 쓰기 락을 사용
- NON_REPEATABLE READ를 방지
비관적 락 PESSIMISTIC_FORCE_INCREMENT 비관적 락 + 버전정보를 강제로 증가
기타 NONE 락을 걸지 않음
(단, @Version이 있으면 자동적으로 낙관적 락이 적용됨)
기타 READ = OPTIMISTIC
기타 WRITE = OPTIMISTIC_FORCE_INCREMENT

 

참고자료

- 서적 - 자바 ORM 표준 JPA 프로그래밍 제 16장 - 김영한 지음

'SpringFramework > JPA' 카테고리의 다른 글

QueryDSL 상위버전 설정 및 예제  (0) 2021.12.13
JPA - N+1 문제  (0) 2021.11.29
JPA - 스프링 데이터 JPA  (0) 2021.11.23
JPA - QueryDSL  (0) 2021.11.19
JPA - Criteria  (0) 2021.11.19