트랜잭션과 @Transactional

Transaction이란

Spring은 @Transactional 어노테이션을 이용해 선언적 트랜잭션 처리를 할 수 있도록 지원한다.

그렇다면 먼저, 트랜잭션이란 뭘까?

트랜잭션이란, DB의 상태를 변화시키기 위해 진행하는 작업의 단위를 말한다.

CRUD의 C U D에 해당하는 SQL 질의어(INSERT, DELETE, UPDATE)를 통해 DB에 접근하는 것을 말한다.

이 때, [SQL 질의어 하나 == 직업의 단위] 가 아니다.

예시 : 송금

A의 계좌에서 B의 계좌로 100만원을 송금한다고 가정하자. 그리고 프로세스는 다음과 같다고 하자.

  1. 출금 : A의 계좌에서 100만원이 차감된다. (UPDATE)
  2. 입금 : B의 계좌에 100만원을 추가한다. (UPDATE)

그런데 그 짧은 순간 네트워크에 문제가 생겼고, 1번만 실행되었다면, 송금은 실패하고 A의 계좌에서는 100만원이 사라져버린다.

이런 상황을 방지하기 위해 거래의 성사인 COMMIT과 작업을 취소하고 이전 상태로 되돌리는 ROLLBACK의 기준이 되어주는 것이 트랜잭션이다. (TCL의 기준)

1번과 2번의 과정으로 나뉘어 진행되지만 송금과정 전체를 하나의 트랜잭션으로 보고 모든 프로세스가 종료되고 난 후 그 때 COMMIT을 발생시킨다. 만약 네트워크의 문제로 위의 예시같은 상황이 발생한다면 ROLLBACK을 발생시킨다.

즉, [SQL 질의어의 집합 == 작업의 단위, 트랜잭션] 이다.

 

Transaction의 특징 (ACID)

ACID는 Atomicity, Consistency, Isolation, Durability의 약어로, 각각에 대한 설명은 다음과 같다.

  • Atomicity - 원자성
    트랜잭션은 DB에 반영되거나 반영되지 않아야 한다. 위 송금 예시에서, 1번만 반영되고 2번은 반영되지 않는 등의 상태가 반영되어서는 안된다.
  • Consistency - 일관성
    트랜잭션 결과는 항상 일관성을 지녀야 한다. 즉, 트랜잭션 진행도중 DB에 변경이 일어나더라도 트랜잭션 시작 지점의 DB 상태로 진행되어야 한다.
  • Isolation - 독립성/격리성
    여러 트랜잭션이 실행되는 경우 다른 트랜잭션의 연산에 간섭이 일어나면 안된다.
  • Durability - 지속성
    트랜잭션이 완료되면 그 결과는 영구 반영되어야 한다.
    하지만 ACID를 너무 따르게 되면 독립성은 보장되어도 동시성이 떨어지는 등의 성능저하가 일어날 수 있다. 이를 보완하기 위한 트랜잭션 격리 수준 등의 설정이 있다.

 


@Transactional

트랜잭션 처리를 위해서는 트랜잭션이 필요한 메서드, 클래스, 인터페이스 위에 어노테이션 @Transaction을 추가하면 된다. (선언적 방식)

이렇게 트랜잭션이 적용된 범위에서는 트랜잭션 기능이 포함된 프록시 객체가 생성되어 COMMIT과 ROLLBACK을 자동으로 진행해준다.

만약 클래스와 메서드 전체에 어노테이션을 붙이면 메서드레벨의 트랜잭션 선언이 우선이 된다.

@Transactional 옵션

  • isolation - 격리레벨, 밑으로 내려갈수록 높은 격리
     
    • DEFAULT
      기본 격리수준, DB의 isolation level을 따름
      Dirty Read 발생 가능 (아직 데이터 변경 적용 안된 uncomitted 데이터 읽기 가능)

    • READ_UNCOMMITED
      COMMIT되지 않는 데이터에 대한 읽기 허용
      Dirty Read 방지 (데이터 변경 동안 다른 사용자는 데이터에 접근 불가능)

    • READ_COMMITED
      COMMIT되지 않는 데이터에 대해 읽기 허용
      Non-Repeatable Read 방지

    • REPEATEABLE_READ
      동일필드에 다중 접속시 전부 동일결과 보장

    • SERIALIZABLEisolation - 격리레벨, 밑으로 내려갈수록 높은 격리
      트랜잭션 완료시까지 그 영역 해당되는 데이터 입력/수정이 불가능
      Phantom Read 방지

  • propagation - 전파속성

    • REQUIRED
      이미 진행중인 트랜잭션 있으면 해당 트랜잭션 따르고, 진행중이 아니면 새로운 트랜잭션을 생성한다.

    • REQUIRES_NEW
      매번 새로운 트랜잭션을 생성한다.

    • SUPPORT
      이미 진행중인 트랜잭션 있으면 해당 트랜잭션 속성을 따르고, 없으면 설정하지 않는다.

    • NOT_SUPPORT
      이미 진행중인 트랜잭션이 있으면 보류한다. 트랜잭션 없이 작업을 수행한다.

    • MANDATORY
      이미 진해웅인 트랜잭션이 있어야만 작업을 수행한다. 없으면 Exception을 발생시킨다

    • NEVER
      트랜잭션이 진행중이지 않을 때 작업을 수행한다. 트랜잭션이 있으면 Exception을 발생시킨다.

    • NESTED
      진행중인 트랜잭션이 있으면 중첩된 트랜잭션이 실행되며 존재하지 않으면 REQUIRED 와 동일하게 실행된다.

  • noRollbackFor
    특정 예외 발생 시 ROLLBACK 처리하지 않는다.

  • rollbackFor
    특정 예외 발생 시 강제로 ROLLBACK 한다.

  • timeout
    지정한 시간 내에 해당 메소스가 완료되지 않을 경우 ROLLBACK을 수행한다.
    Default = -1이며, -1일 경우 no timeout이 적용된다.

  • readOnly
    insert, update, delete 실행 시 예외가 발생한다.
    Default = false이다.

LazyInitializationException

트랜잭션과 @Transactional에 대해 더 알아봐야겠다고 생각하게 된 것은 프로젝트 진행도중 발생한 LazyInitializationException 때문이었다. 이 부분에 대해서는 이후 프로젝트 카테고리에 따로 기술해볼까 한다.

'Spring > Spring 정리' 카테고리의 다른 글

Spring 과 Spring Boot 의 차이  (0) 2022.10.09
API - 3 (PUT, DELETE)  (0) 2022.09.21
REST / REST API / RESTful 간단히 정리  (0) 2022.09.21
API - 2 (GET, POST)  (0) 2022.09.21
DTO  (2) 2022.09.19