| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- MSA
- 서블릿 컨테이너
- securitycontextholderfilter
- redis
- mybatis
- docker
- Routing Key
- 페이징
- Spring
- dockerhub
- JPQL
- docker compose
- Spring Container
- AWS
- Web
- CORS
- Spring Data JPA
- 스프링 부트
- JWT
- kafka
- DLQ
- @Transactional
- 컨테이너
- @ComponentScan
- 지연 로딩
- JdbcTemplate
- Dead Letter Queue
- 쿠버네티스
- JPA
- DI
- Today
- Total
look-forest
스프링 트랜잭션 전파 본문
트랜잭션을 각각 사용하는 것이 아니라 트랜잭션이 이미 진행중인데, 여기에 추가로 트랜잭션을 수행하면 어떻게 될까?
이런 경우 어떻게 동작할지 결정하는 것을 트랜잭션 전파(propagation)라 한다.
스프링은 다양한 트랜잭션 전파 옵션을 제공한다.
전파 기본
지금부터 설명하는 내용은 트랜잭션 전파의 기본 옵션인 REQUIRED 를 기준으로 설명한다.
외부 트랜잭션이 수행중인데, 내부 트랜잭션이 추가로 수행될 경우, 스프링에서 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 트랜잭션을 만들어준다 (커넥션 1개) . 내부 트랜잭션이 외부 트랜잭션에 참여하는 것이다. 이것이 기본 동작이고, 옵션을 통해 다른 동작방식도 선택할 수 있다 (커넥션을 새로 생성하는 경우) .

스프링은 이해를 돕기 위해 논리 트랜잭션과 물리 트랜잭션이라는 개념을 나눈다.
이러한 논리 트랜잭션 개념은 트랜잭션이 진행되는 중에 내부에 추가로 트랜잭션을 사용하는 경우에 나타난다
- 물리 트랜잭션은 우리가 이해하는 실제 데이터베이스에 적용되는 트랜잭션을 뜻한다.
실제 커넥션을 통해서 트랜잭션을 시작(setAutoCommit(false)) 하고, 실제 커넥션을 통해서 커밋, 롤백하는 단위이다. - 논리 트랜잭션은 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위이다.
- 논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶인다.
원칙
- 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
- 하나의 트랜잭션 매니저라도 롤백하면 물리 트랜잭션은 롤백된다.
내부 트랜잭션은 이미 진행중인 외부 트랜잭션에 참여한다.
내부 트랜잭션은 신규 트랜잭션이 아니다. 커넥션을 생성하지 않고, 기존 트랜잭션이 존재하므로 기존 트랜잭션에 참여한다. (내부 트랜잭션을 시작할 때 Participating in existing transaction 이라는 로그 메시지를 확인할 수 있다. 내부 트랜잭션을 시작하거나 커밋할 때는 DB 커넥션을 통해 커밋하는 로그를 전혀 확인할 수 없다.)


트랜잭션 매니저에 커밋을 호출한다고해서 항상 실제 커넥션에 물리 커밋이 발생하지는 않는다. 실제 커넥션에 커밋이나 롤백을 호출하면 물리 트랜잭션이 끝나버리기 때문이다. 따라서 내부 트랜잭션인 경우는 커밋이나 롤백이 실제 수행되지 않는다.
신규 트랜잭션인 경우에만 실제 커넥션을 사용해서 물리 커밋과 롤백을 수행한다. 이렇게 트랜잭션이 내부에서 추가로 사용되면 트랜잭션 매니저에 커밋하는 것이 항상 물리 커밋으로 이어지지 않는다.
즉, 신규 트랜잭션인 경우에만 실제 물리적으로 커넥션을 생성하고, 커밋과 롤백을 수행한다.
따라서, 내부 트랜잭션이 커밋되는데, 외부 트랜잭션이 롤백되는 경우는 전체 롤백이 된다. (내부 커밋 수행x)
그럼 내부 트랜잭션은 롤백되는데, 외부 트랜잭션이 커밋되는 경우는 어떨까?
내부 트랜잭션이 롤백을 했지만, 물리 트랜잭션에 영향을 주지 않는다. 그런데 외부 트랜잭션은 커밋 해버리는 상황.
전체를 롤백해야 하는데, 스프링은 이 문제를 어떻게 해결할까?
내부 트랜잭션을 롤백하면 실제 물리 트랜잭션은 롤백하지 않는다. 대신에 기존 트랜잭션을 롤백 전용으로 표시한다!

- 내부 논리 트랜잭션이 롤백되면 롤백 전용 마크를 표시한다.
Participating transaction failed - marking existing transaction as rollback-only - 외부 트랜잭션을 커밋할 때 롤백 전용 마크를 확인한다. 롤백 전용 마크가 표시되어 있으면 물리 트랜잭션을 롤백하고, UnexpectedRollbackException 예외를 던진다. (기대하지 않은 롤백이 발생했다는 것을 명확하게 알려준다)
Global transaction is marked as rollback-only but transactional code requested commit
스프링 트랜잭션 전파 - REQUIRES_NEW
이번에는 외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 사용하는 방법에 대해서 알아보자.
외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 각각 별도의 물리 트랜잭션을 사용하는 방법이다. 그래서 커밋과 롤백도 각각 별도로 이루어지게 된다. (별도의 물리 트랜잭션을 가진다는 뜻은 DB 커넥션을 따로 사용한다는 뜻)
이 방법은 내부 트랜잭션에 문제가 발생해서 롤백해도, 외부 트랜잭션에는 영향을 주지 않는다. 반대로 외부 트랜잭션에 문제가 발생해도 내부 트랜잭션에 영향을 주지 않는다.
이렇게 물리 트랜잭션을 분리하려면 내부 트랜잭션을 시작할 때 REQUIRES_NEW 옵션을 사용하면 된다. 이 전파 옵션을 사용하면 내부 트랜잭션을 시작할 때 기존 트랜잭션에 참여하는 것이 아니라 새로운 물리 트랜잭션을 만들어 시작한다.


- 트랜잭션 매니저는 REQUIRES_NEW 옵션을 확인하고, 기존 트랜잭션에 참여하는 것이 아니라 새로운 트랜잭션을 시작한다
- 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다.
이때 con1 은 잠시 보류되고, 지금부터는 con2 가 사용된다. (내부 트랜잭션을 완료할 때 까지 con2 가 사용된다.)

- REQUIRES_NEW 옵션을 사용하면 물리 트랜잭션이 명확하게 분리된다.
- REQUIRES_NEW 를 사용하면 데이터베이스 커넥션이 동시에 2개 사용된다는 점을 주의해야 한다
스프링 트랜잭션 전파 - 다양한 전파 옵션
- REQUIRED - 트랜잭션이 필수. 기존 트랜잭션이 없으면 생성하고, 있으면 참여한다. (기본 설정)
- REQUIRES_NEW - 항상 새로운 트랜잭션을 생성한다.
- SUPPORT - 트랜잭션을 지원한다는 뜻. 기존 트랜잭션이 없으면, 없는대로 진행하고, 있으면 참여한다.
- NOT_SUPPORT - 트랜잭션을 지원하지 않는다. 트랜잭션 없이 진행한다. (기존 트랜잭션은 보류한다)
- MANDATORY - 의무사항이다. 트랜잭션이 반드시 있어야 한다. 기존 트랜잭션이 없으면 예외가 발생한다
- NEVER - 트랜잭션을 사용하지 않는다. 기존 트랜잭션이 있으면 예외가 발생한다
- NESTED - 중첩 트랜잭션을 만든다.
트랜잭션 전파와 옵션
isolation , timeout , readOnly 는 트랜잭션이 처음 시작될 때만 적용된다. 트랜잭션에 참여하는 경우에는 적용되지 않는다
참고 자료 & 이미지 출처
스프링 DB 2편 - 데이터 접근 기술 (김영한 님)
'Spring > Spring 데이터 접근 - 활용 기술' 카테고리의 다른 글
| 트랜잭션 전파 활용 (0) | 2024.09.07 |
|---|---|
| 스프링 트랜잭션 이해 (0) | 2024.09.02 |
| 데이터 접근 기술 - 활용 방안 (2) | 2024.09.02 |
| QueryDSL : type-safe Query (0) | 2024.08.31 |
| 스프링 데이터 JPA (0) | 2024.08.18 |