본문 바로가기
Framework & Lib/스프링

spring 트랜잭션 전파 속성 - 중첩 트랜잭션 구조의 동작 방식

by 코딩공장공장장 2025. 2. 1.

트랜잭션 전파 속성


트랜잭션의 전파속성이란 중첩된 트랜잭션 구조가 나왔을 때, 어떻게 동작 시킬지를 결정하는 속성이다.

DB수준에서 중첩 트랜잭션이라는 것은 존재하지 않는다. 

부분적으로 sql구문을 롤백시킬수는 있으나 중첩 트랜잭션은 존재하지 않는다.

프로그래밍단에서 @Transactional 어노테이션이 부착된 메서드의 중첩 구조가 나타날 수 있고
스프링에서는 중첩된 구조에서 다양한 옵션으로 트랜잭션을 사용할 수 있게 하며, 
완전히 독립적으로 동작할 수 있는 옵션 또한 제공하고 있다.

 

* 앞으로 설명한 용어에서 부모-자식 트랜잭션이라는 표현은 프로그래밍 서버 기준이며 실제 DB에서 부모-자식 트랜잭션이라는 것은 존재하지 않는것에 주의하자.

 

DB 트랜잭션

[중첩 트랜잭션 미지원]

start transaction;
insert into tx_test values (1, "aaa");
    start transaction;
    insert into tx_test values (2, "aaa");
    rollback;
rollback;

위 쿼리의 결과로 id가 1인 row가 추가될 것이다.

두번째 start transaction을 사용하며 첫번째 트랜잭션이 auto commit된다.

사실상 마지막에 있는 rollback 구문은 의미가 없는 것이다.

DB 수준에서 중첩 트랜잭션은 지원되지 않는다.

 

[savepoint를 통한 부분 롤백]

start transaction;
insert into tx_test values (1, "aaa");
	SAVEPOINT t1; -- 여기까지 저장
	insert into tx_test values (2, "bbb");
		SAVEPOINT t2; -- 여기까지 저장
	rollback to t1; -- t1 밑으로 롤백 
commit;

 

savepoint는 트랜잭션 내부에서 부분 롤백을 지원하는 sql 구문이다.

이 구문을 사용하면 중첩 트랜잭션처럼 동작시킬 수 있다.

위에서 SAVEPOINT t1; 을 통해 트랜잭션 내부에 이름을 선언하고,

rollback시 해당 이름을 사용하면 부분 롤백을 할 수 있다.

허나, commit to [save point 이름]이라는 옵션은 없다.

save point 여러개 지정하여 rollback을 여러번 수행할 수 있으나 commit은 단 한번 수행에 모든 구문을 반영한다.

즉, db 수준에서 savepoint를 통해 중첩 트랜잭션처럼 만들어 사용하더라도 내부 구문을 롤백하고 외부 구문을 커밋할 수 있으나, 내부 구문을 커밋하고 외부 구문을 롤백할 수는 없다.

따라서 완벽한 중첩 트랜잭션이라고 할 수 없다.

 

Spring 트랜잭션

Sping에서는 중첩 트랜잭션을 지원한다.

구체적인 내용은 각 트랜잭션 전파 속성에서 설명하겠다.

전파속성 설명
REQUIERED - 기존 트랜잭션 존재한다면 합류, 그렇지 않으면 새로운 트랜잭션 생성
- 부모나 자식 상관없이 하나라도 예외시 전체 롤백
   (사실상 가장 바깥쪽 트랜잭션만 유효하고 내부에서 선언된 것 의미 없음)
SUPPORTS 트랜잭션을 필요로 하지 않지만 진행 중인 트랜잭션이 존재하면 트랜잭션 사용
(트랜잭션 미 존재시에도 메소드 정상 동작)
MANDATORY 부모 트랜잭션에 합류(부모 트랜잭션 미존재시 예외 발생)
REQUIRES_NEW - 무조건 새로운 트랜잭션 생성
- 부모-자식 트랜잭션간 독립적으로 롤백 및 커밋 가능
NOT_SUPPORTED 트랜잭션 중단(예외 발생은 안함)
NEVER 트랜잭션 사용 안함(진행 중 트랜잭션 존재시 예외 발생)
NESTED - 부모 트랜잭션 존재한다면 합류, 그렇지 않으면 새로운 트랜잭션 생성
- But, 자식 예외 부모에 영향 안줌 (부모 예외시에만 자식까지 롤백)

 

REQUIERED spring의 기본 트랜잭션 전파 속성으로 부모와 자식 트랜잭션을 하나의 트랜잭션으로 사용하는 구조이다.

하나의 트랜잭션으로 합쳐진다는 표현이 마치 트랜잭션을 새롭게 생성하고 두 개를 합친다는 것처럼 느껴지지만,

실제로는 하나의 트랜잭션을 함께 사용한다.

첫번째 @Transactional을 적용한 메서드에 대해 start transaction; 쿼리를 적용하고 이후 @Transactional이 선언된 메서드는 해당 트랜잭션 내부에서 수행된다.

 

REQUIRES_NEW의 경우 무조건 새로운 트랜잭션 생성이다. 중첩 구조이다. 

DB에서는 중첩 트랜잭션이 없기에, spring에서 각각 별개의 커넥션을 연결하여 독립적으로 트랜잭션으로 동작시킨다.

프로그래밍 서버 입장에서는 트랜잭션 어노테이션이 부착된 메서드가 중첩 구조를 이루어 부모-자식 관계라고 볼 수 있지만, 실제로는 각각 별도의 커넥션을 사용하여 트랜잭션을 동작시키므로 각 트랜잭션이 서로 영향을 미치지 않는다. 

따라서 부모-자식 간의 메서드 중 예외가 발생하더라도 서로에게 영향을 미치지 않는다.

 

NESTED는 savepoint 쿼리를 통해 동작시키는 구조이다.
JDBC가 NESTED 옵션을 제공하는데 해당 옵션을 그대로 사용한다.
(JPA에서는 NESTED 옵션 미지원된다.)

savepoint를 사용하기에 자식 트랜잭션의 롤백만 가능하고 커밋은 반드시 함께 일어난다.

따라서 자식 메서드에서 예외가 발생하더라도 부모 메서드는 정상 커밋 수행될 수 있다.

허나, 반대의 경우는 나타날 수 없다.

 

SUPPORTS, MANDATORY, NOT_SUPPORTED, NEVER는 중첩 구조에서 트랜잭션 존재여부에 따라 예외를 발생시키거나 트랜잭션 사용 여부를 결정하지를 정하는 옵션이다.

 

사실상 DB에서 제공하지 않는 중첩 트랜잭션 옵션은 REQUIRES_NEW 하나 뿐이다.

반응형