트랜잭션 전파레벨
@Transactional이란?
어노테이션 기반으로 설정하는 트랜잭션 구성 방식. 어노테이션 방식은 선언적 트랜잭션이라고도 불린다. 선언 시 트랜잭션 기능이 적용된 프록시 객체가 생성된다는 특징이 있다.
@Transactional로 생성된 프록시 객체는 @Transactional이 적용된 메소드가 호출될 경우 PlatformTransactionManager를 사용하여 트랜잭션을 시작하고, 정상 여부에 따라 Commit/Rollback 동작을 수행한다.
즉, 트랜잭션 처리를 JDK Dynamic Proxy 객체에게 대신 위임하여 AOP로 동작하게 한다.
어노테이션으로 트랜잭션을 구성할 경우 다음과 같은 옵션을 줄 수 있다.
- propagation
- 트랜잭션 전파 옵션으로 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정하는 옵션이다.
- isolation
- 트랜잭션 격리 수준을 설정한다. 즉, 트랜잭션에서 일관성 없는 데이터 허용 수준을 설정한다.
- Timeout
- 지정한 시간 내에 메서드 수행이 완료되지 않으면 rollback이 동작하도록 설정한다.
- readOnly
- 트랜잭션을 읽기 전용으로 설정한다.
- noRollbackFor
- 특정 예외 발생 시 rollback이 동작하지 않도록 설정한다.
- rollbackFor
- 특정 예외 발생 시 rollback이 동작하도록 설정한다.
단, private/protected 메서드는 @Transactional어노테이션을 무시한다.
- Transactional은 AOP를 사용하여 Proxy객체의 형태로 동작하기 때문이다. 즉, 외부에서 접근 가능한 메소드만 @Transactional 설정이 가능하다.
여기서 오늘은 propagation : 트랜잭션 전파레벨에 대해서 알아볼 것이다.
트랜잭션 전파레벨이란?
스프링에서 @Transactional을 클래스 또는 메소드 레벨에 명시하면 해당 메소드 호출 시 지정된 트랜잭션이 작동한다.
단, 해당 클래스의 Bean을 다른 클래스의 Bean에서 호출할 때만 @Transactional을 인지하고 작동한다. 같은 Bean내에서 @Transactional이 명시된 다른 메서드를 호출해도 작동하지 않는다. 그 이유는 Spring은 내부적으로 AOP를 통해 해당 어노테이션을 인지하여 프록시를 생성하여 트랜잭션을 자동 관리하기 때문이다.
트랜잭션 전파옵션이란?
트랜잭션 동작 도중 다른 트랜잭션을 호출(실행)하는 상황에 선택할 수 있는 옵션이다.
이미 트랜잭션이 진행중일 때 추가 트랜잭션 진행을 어떻게 할 것인지 결정하는 것이 전파 속성이다.
@Transactional의 propagation 속성을 통해 피호출 트랜잭션의 입장에서는 호출한 쪽의 트랜잭션을 그대로 사용할 수도 있고, 새롭게 트랜잭션을 생성할 수도 있으며 에러를 발생시키는 등 여러 선택을 할 수 있다.
물리 트랜잭션 vs 논리 트랜잭션
데이터베이스에서 제공하는 트랜잭션 개념과 스프링이 구분하는 트랜잭션이랑을 나눈 것이다.
실제 트랜잭션은 데이터베이스에서 제공하는 기술로 커넥션 객체를 통해 처리한다. 즉, 트랜잭션 하나를 사용한다는 것은 하나의 커넥션 객체를 사용한다는 것이고, 물리적인 개념이라고 볼 수 있다.
스프링 입장에서 기존 트랜잭션이 진행중일 때 내부에서 또 다른 트랜잭션이 사용된다는 것은 트랜잭션 커넥션 객체를 새로 사용한다는 것이 아니라, 스프링 내부의 트랜잭션 영역을 구분하기 위한 논리적인 개념이다. 즉, 스프링의 외부 트랜잭션과 내부 트랜잭션은 1개의 물리 트랜잭션(커넥션)을 사용한다.
- 물리 트랜잭션: 실제 데이터베이스에 적용되는 트랜잭션으로, 커넥션을 통해 커밋/롤백 하는 단위
- 논리 트랜잭션: 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위
기존 트랜잭션이 진행중일 때 또 다른 트랜잭션이 사용되면 복잡한 상황이 발생한다. 스프링은 논리 트랜잭션이라는 개념을 도입함으로써 상황에 대한 설명을 쉽게 만들고, 다음과 같은 원칙을 세울 수 있다.
- 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋됨
- 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백됨
스프링의 트랜잭션 전파 옵션 종류
총 7개의 전파 옵션이 존재한다. 트랜잭션 전파 옵션들은 그 중 대표적인 REQUIRED와 REQUIRES_NEW를 바탕으로 진행된다. 각각이 무엇을 의미하는지 알아보자.
- REQUIRED
- 스프링이 제공하는 기본(DEFAULT) 전파 속성이다.
- 2개의 논리 트랜잭션을 묶어 1개의 물리 트랜잭션을 사용하는 것이다.
- 내부 트랜잭션은 기존에 존재하는 외부 트랜잭션에 참여하게 된다.
- 즉, 외부 트랜잭션을 계속 이어간다는 뜻이다.
- 새로운 물리 트랜잭션을 사용하는 것이 아닌 존재하는 물리 트랜잭션을 사용한다.
- 내부 트랜잭션에서 커밋을 호출해도 즉시 커밋되지 않고 외부 트랜잭션이 최종적으로 커밋될 때 실제로 커밋된다.
- 논리 트랜잭션들 중에서 1개라도 롤백되었다면 롤백된다.
- REQUIRES_NEW
- 외부 트랜잭션과 내부 트랜잭션을 완전히 분리하는 전파 속성이다.
- 2개의 물리 트랜잭션이 사용된다. 서로 다른 데이터베이스 커넥션이 사용된다.
- 각각 트랜잭션 별로 커밋과 롤백이 수행된다.
- 내부 트랜잭션 롤백이 외부 트랜잭션 롤백에 영향을 주지 않는다.
- 즉, 내부 트랜잭션의 롤백 호출은 실제 커넥션에 롤백을 호출하는 것이므로 트랜잭션이 끝나게 된다.
- 1개의 HTTP 요청에 대해 2개의 커넥션이 사용된다.
- 내부 트랜잭션이 처리 중일 때는 외부 트랜잭션이 커넥션을 가지고 내부 트랜잭션이 커밋될 때 까지 기다리는데, 이러한 행위는 데이터베이스 커넥션을 고갈시킬 수 있다.
- 조심해서 사용해야 하며, REQUIRES_NEW 없이 해결 가능하다면 대안책(별도의 클래스 두기)등을 사용하는 것이 좋다.
7가지 속성을 다시 요약해서 정리해보자.
- REQUIRED (DEFAULT)
- 트랜잭션이 필요하다.(없으면 새로 만든다.)
- 기존 트랜잭션 존재: 기존 트랜잭션에 참여한다.
- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
- SUPPORTS
- 트랜잭션이 있으면 지원한다. (트랜잭션이 없어도 된다.)
- 기존 트랜잭션 존재: 기존 트랜잭션에 참여한다.
- 기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
- MANDATORY
- 트랜잭션이 반드시 필요하다.
- 기존 트랜잭션 존재: 기존 트랜잭션에 참여한다.
- 기존 트랜잭션 없음: IllegalTransactionStateException 예외가 발생한다.
- REQUIRES_NEW
- 항상 새로운 트랜잭션이 필요하다.
- 기존 트랜잭션 존재: 기존 트랜잭션을 보류시키고 새로운 트랜잭션을 생성한다.
- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
- NOT_SUPPORTED
- 트랜잭션을 지원하지 않는다. (트랜잭션 없이 진행한다.)
- 기존 트랜잭션 존재: 기존 트랜잭션을 보류시키고 트랜잭션 없이 진행한다.
- 기존 트랜잭션 없음: 트랜잭셕 없이 진행한다.
- NEVER
- 트랜잭션을 사용하지 않는다. (기존 트랜잭션도 허용하지 않는다.)
- 기존 트랜잭션 존재: IllegalTransactionStateException 예외가 발생한다.
- 기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
- NESTED
- 중첩(자식) 트랜잭션을 생성한다.
- 기존 트랜잭션 존재: 중첩 트랜잭션을 만든다.
- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
NESTED vs REQUIRES_NEW
NESTED: 이미 진행중인 트랜잭션에 중첩(자식) 트랜잭션을 만드는 것. 독립적인 트랜잭션을 만드는 REQUIRES_NEW와 다르다. NESTED에 의한 중첩 트랜잭션은 부모 트랜잭션의 영향(커밋과 롤백)을 받지만, 중첩 트랜잭션이 외부에 영향을 주지 않는다.
즉, 중첩 트랜잭션이 롤백되어도 외부 트랜잭션은 커밋이 가능하지만 외부 트랜잭션이 롤백되면 중첩 트랜잭션은 함께 롤백된다. NESTED는 JDBC의 savepoint 기능을 사용하는데, DB 드라이버가 이 기능을 지원하는지 확인이 필요하며 JPA서는 사용이 불가능하다.
reference
https://n1tjrgns.tistory.com/266
https://okky.kr/articles/1151991
https://data-make.tistory.com/738
https://cobbybb.tistory.com/17
'Spring' 카테고리의 다른 글
[Spring] DI 의존성 주입 방식과 생성자 주입을 사용해야 하는 이유 (0) | 2022.11.22 |
---|---|
[Spring] ArgumentResolver란? (0) | 2022.11.18 |
[Spring] 필터와 인터셉터 (Filter vs Interceptor) (0) | 2022.11.16 |
[Spring] SpringBootTest 테스트 격리 방법 (0) | 2022.11.15 |
[Spring] Spring Bean Validation의 @Valid vs @Validated (0) | 2022.11.14 |