[Spring Boot] Annotation @Transactional
Spring BootSpring AOP를 기반으로 트랜잭션을 처리하는 선언적 트랜잭션 어노테이션
Transaction 정의
데이터베이스의 데이터/상태를 조작하는 작업 단위
한 번에 수행되어야 하는 연산들
ACID 원칙
원자성 Atomicity
- 한 트랜잭션 내에서 실행한 작업들은 한 번에 처리한다.
- 모두 성공하거나 모두 실패한다. 부분적으로 성공하는 일이 없도록 보장한다.
일관성 Consistency
- 일관성 있는 데이터베이스 상태를 보장한다
격리성 Isolation
- 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않게 격리한다.
영속성 Durability
- 트랜잭션을 성공적으로 마치면 결과를 영구적으로 저장한다.
선언 방법
메소드, 클래스, 인터페이스 위에 선언한다.
적용된 범위에서는 트랜잭션 기능이 포함된 프록시 객체가 생성되어 자동으로 commit/rollback을 실행한다.
프록시 객체에서 접근 가능한 레벨로 지정해야 한다. (private 사용 불가)
트랜잭션은 객체 외부에서 처음 진입하는 메서드를 기준으로 동작한다.
kotlin
@Transactional
fun signUp()
속성 설정
kotlin
@Transactional(propagation = Propagation.REQUIRED)
트랜잭션 전파
트랜잭션 동작 도중에 다른 트랜잭션을 호출할 때 어떻게 할 것인지 설정한다.
- REQUIRED: 진행 중인 트랜잭션이 있으면 참여하고, 없으면 새로운 트랜잭션을 생성한다.
- SUPPORTS: 진행 중인 트랜잭션이 있으면 참여하고, 없으면 트랜잭션을 없이 작업을 진행한다.
- MANDATORY: 진행 중인 트랜잭션이 있으면 참여하고, 없으면 예외를 발생시킨다. 독립적인 작업을 하면 안될 때 사용한다.
- REQUIRES_NEW: 항상 새로운 트랜잭션을 생성한다. 진행 중인 트랜잭션이 있으면 보류시키고 해당 트랜잭션을 실행시킨다.
- NOT_SUPPORTED: 트랜잭션 없이 작업을 진행한다. 진행 중인 트랜잭션이 있으면 보류시킨다.
- NEVER: 트랜잭션 사용 금지. 진행 중인 트랜잭션이 있다면 예외를 발생시킨다.
- NESTED: 진행 중인 트랜잭션이 있으면 중첩된 트랜잭션을 생성하고, 없으면 새로운 트랜잭션을 생성한다.
- 중첩된 트랜잭션은 부모 트랜잭션에서 발생하는 커밋과 롤백에 영향을 받지만, 부모 트랜잭션은 중첩된 트랜잭션에 영향을 받지 않는다.
kotlin
@Transactional(isolation = Isolation.DEFAULT)
트랜잭션 격리 수준(Isolation Level)을 설정한다.
트랜잭션에서 일관성이 없는 데이터를 접근할 수 있게 허용하는 수준을 설정한다.
Isolation Level이 낮아질 수록 동시성이 높아지고, 무결성이 낮아져 문제가 발생할 가능성이 커진다.
- DEFAULT: 기본 격리 수준
- READ_UNCOMMITTED
- Level 0
- SELECT가 수행된 데이터에 Shared Lock이 걸리지 않는다.
- 변경 사항이 완료(commit)되기 전에 다른 트랜잭션이 해당 행을 읽을 수 있다.
- A 트랜잭션이 변경하고 완료하지 않은 데이터(Uncommitted or Dirty)를 B 트랜잭션이 읽을 수 있다.
- Dirty Read, Non-Repeatable Read, Phantom Read 발생 가능
- READ_COMMITTED
- Level 1. Read Lock
- 가장 많이 선택되는 격리 수준. 오라클 DBMS 기본값
- SELECT가 수행된 행에 Record Lock이 걸려서 변경 사항이 완료되기 전까지 다른 트랜잭션이 읽을 수 없다.
- A 트랜잭션이 변경한 데이터를 완료하기 전까지 B 트랜잭션이 해당 데이터를 SELECT할 수 없다.
- B 트랜잭션은 변경 전 데이터(Undo 영역에 백업된 데이터)를 SELECT 한다.
- MVCC(Multi Version Consurrency Control) 변경 방식을 사용
- Non-Repeatable Read, Phantom Read 발생 가능
- REPEATABLE_READ
- Level 2. Gap Lock
- MySQL InnoDB Storage Engine 기본값
- 동일한 SELECT 문장을 수행했을 때 항상 같은 결과를 보장한다. (정합성)
- A 트랜잭션이 완료되기 전까지 B 트랜잭션은 SELECT, UPDATE, DELETE를 할 수 없다.
- INSERT는 가능하기 때문에 동일 트랜잭션에서 Read되는 row 수가 달라질 수 있다.
- Phantom Read 발생 가능하지만 MySQL-InnoDB에서는 MVCC 특성에 의해 발생하지 않음
- SERIALIZABLE
- Level 3
- 가장 단순하면서 가장 엄격한 격리 수준
- A 트랜잭션이 완료되기 전까지 B 트랜잭션은 어떠한 작업도 수행하지 못한다.
- DeadLock에 걸리기 쉽다.
kotlin
@Transactional(noRollbackFor = [SafeException::class])
예외 제외. 특정 예외 발생 시 Rollback하지 않는다.
kotlin
@Transactional(rollbackFor = [UserCustomException::class])
예외 추가. 특정 예외 발생 시 Rollback한다.
kotlin
@Transactional(timeout = TransactionDefinition.TIMEOUT_DEFAULT)
트랜잭션 수행 시간을 제한한다. 지정한 시간 내에 작업이 완료되지 않으면 Rollback한다.
default: -1 (no timeout)
kotlin
@Transactional(readOnly = true)
읽기 전용 트랜잭션. 트랜잭션 내에서 데이터를 조작할 수 없다.
true로 설정하면 INSERT, UPDATE, DELETE 실행 시 예외를 발생시킨다.
default: false
REPEATABLE READ Test
Reference