영호
[Spring] @Transactional 의 REQUIRED, REQUIRES_NEW 트랜잭션 생성 과정 본문
들어가면서,
트랜잭션 전파 옵션에는 REQUIRED, REQUIRES_NEW 등등이 있다. 그 중에서 이번에는 REQUIRED, REQUIRES_NEW 옵션일 때 트랜잭션 생성 과정에 대해 알아볼 예정이다.
- REQUIRED 는 어떻게 기존 트랜잭션이 존재하면서 해당 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 만들까?
- REQUIRES_NEW 는 어떻게 매번 새로운 트랜잭션을 만들까?
이 부분에 대해 알아볼 때 핵심적인 기능을 하는 클래스는 AbstractPlatformTransactionManager, JpaTransactionManager, TransactionSynchronizationManager
가 있다.
이 클래스들이 기존 트랜잭션이 있는지 확인한다. 없으면 새로운 트랜잭션 설정을 해주고, 기존 트랜잭션이 있으면 REQUIRED, REQUIRES_NEW 옵션에 맞게 트랜잭션 설정을 해준다.
REQUIRED
[REQUIRED] 첫 트랜잭션 생성 과정
처음 트랜잭션이 생성되는 경우를 우선 살펴보자. 우선 트랜잭션 관련 인스턴스를 생성하고 여기에 트랜잭션 관련 리소스를 할당해주는 과정을 거친다. 아래는 트랜잭션 생성 시 초반에 호출되는 과정이다.


눈 여겨 봐야할 부분은 JpaTransactionManager 에서 emHolder 가 null 이 반환되고, txObject 의 entityManagerHolder 도 null 인 상태로 반환된다는 것이다. newEntityManagerHolder 의 경우 다른 코드가 호출되면서 true 로 바뀌게 된다.
이렇게 만들어진 인스턴스를 토대로 기존에 트랜잭션이 존재하는지 확인 하는데 그 과정은 아래 코드를 통해 이루어진다.

entityManagerHolder 가 null 이기 때문에 기존에 존재하던 트랜잭션이 없다고 판단한다. 이후 AbstractPlatformTransactionManager.startTransaction()
이 실행되면서 트랜잭션 생성이 마무리 된다.
그리고, 이 과정에서 TransactionSynchronizationManager.bindResource()
를 호출한다. 그래서 이후 생성되는 트랜잭션은 TransactionSynchronizationManager 에 관련 자원이 있기 때문에 기존 트랜잭션이 존재한다고 판단된다.

[REQUIRED] 기존에 트랜잭션이 존재하는 경우
첫 트랜잭션이 생성되는 과정과는 다르게 이번엔 TransactionSynchronizationManager 에 값이 있기 때문에 트랜잭션 관련 인스턴스의 entityManagerHolder가 null 아니라는 것이다.

이로 인해 AbstractPlatformTransactionManager.isExistringTransaction()
이 true 가 되어 this.handleExistingTransaction()
이 호출된다.

아래 캡쳐본에서 JpaTransactionManager 의 박스친 부분을 보면 newTransaction = false 로 설정되는 것을 볼 수 있다. 즉, 현재 만들어지는 트랜잭션은 새로운 트랜잭션이 아니라는 것이다. 이는 나중에 COMMIT 에 영향을 미친다.

이 과정에서 TransactionSynchronizationManager.bindResource()
가 호출되지 않는다.
REQUIRES_NEW
REQUIRES_NEW 는 기존에 트랜잭션이 존재하더라도 새롭게 트랜잭션을 생성한다. REQUIRES_NEW 옵션으로 처음 트랜잭션을 생성하는 것과 REQUIRED 옵션으로 생성하는 것과 과정은 동일하기 때문에 바로 기존 트랜잭션이 존재하는 경우에 대해 알아보자.
REQUIRES_NEW 옵션이라도 트랜잭션이 존재하는 상황에서는 isExistingTransaction() == true
이다. 그래서 handleExistingTransaction()
이 호출되는데, 해당 메서드 실행 과정이 REQUIRED 와 다르다.
REQUIRED 는 handleExistingTransaction()
의 else 블록이 실행됐지만 REQUIRES_NEW 는 다른 블록이 실행된다. 다른 블록이 실행되는 이유는 definition 필드가 다르기 때문이다. definition 에는 propagation, isolation 정보가 있다.
그리고 한 가지 더 주목할 점은 첫 트랜잭션을 생성할 때 실행되는 startTransaction()
이 실행된다는 것이다.

이를 통해, REQUIRES_NEW 로 만들어진 트랜잭션은 newTransaction 속성이 true 로 만들어지고, doBegin()
을 통해 해당 트랜잭션 관련 리소스도 TransactionSynchronizationManager에 저장된다. 물론 기존 값에 대한 처리도 같이 해주고 있다.
결론
디버깅을 통해 REQUIRED 는 어떻게 기존 트랜잭션에 참여하고, REQUIRES_NEW 는 어떻게 매번 새로운 트랜잭션을 만드는지 살펴봤다.
간단하게 결론만 요약하자면 AbstractPlatformTransactionManager.handleExistingTransaction()
메서드의 분기문에 의해 REQUIRED, REQUIRES_NEW 동작이 달라지는 것을 확인할 수 있었다.
혹시 잘못된 부분 있으면 언제든 댓글 달아주세요~
'Spring' 카테고리의 다른 글
[Spring] @TransactionalEventListener 에서 CUD 가 안되는 이유 (0) | 2023.10.15 |
---|---|
[Spring] 외부 API 와 비즈니스 로직 분리하기 (0) | 2023.10.15 |
[Spring] @Transactional 물리 트랜잭션과 논리 트랜잭션 커밋 동작 차이 (2) | 2023.10.14 |
하나의 객체에서 여러 Builder 사용해보기 (feat. Builder 컴파일 과정, lombok) (2) | 2023.09.07 |
[Spring] test context caching (0) | 2023.05.21 |