Spring ‐ 스프링 트랜잭션 전파 - dnwls16071/Backend_Study_TIL GitHub Wiki
📚 스프링 트랜잭션 전파
[ 트랜잭션 두 번 사용 ]
- 트랜잭션이 각각 수행되면서 사용되는 DB 커넥션은 다르다.
[ 전파 기본 - REQUIRED 옵션 디폴트 ]
- 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
- 하나의 논리 트랜잭션이라도 롤백이 된다면 물리 트랜잭션은 롤백된다.
- 외부 트랜잭션이 시작된다.
- 트랜잭션 매니저는 생성자를 통해 주입받는 데이터 소스를 통해 커넥션을 생성한다.
- 생성한 커넥션을 수동 커밋 모드로 전환하면서 실제 트랜잭션을 시작한다. → 물리 트랜잭션 시작
- 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다.
- 트랜잭션 매니저는 트랜잭션 생성 결과를 TransactionStatus에 담아서 반환한다. 이 때, 새로운 트랜잭션이므로 신규 트랜잭션으로 분류가 된다.
- 로직1이 수행될 때 커넥션이 필요한 경우 트랜잭션 동기화 매니저로부터 커넥션을 확보해 사용한다.
- 내부 트랜잭션을 시작한다.
- 이 때, 내부 트랜잭션의 경우 생성자를 통해 주입받는 데이터 소스를 통해 커넥션을 생성하지 않고 트랜잭션 동기화 매니저를 통해 기존 트랜잭션이 존재하는지 확인한다.
- 기존 트랜잭션(외부 트랜잭션)이 있기 때문에 기존에 시작된 트랜잭션을 자연스럽게 사용하게 된다.
- 트랜잭션 매니저에서 트랜잭션 생성 결과를 TransactionStatus에 담아서 반환하는데 이 때, 기존 트랜잭션을 이어서 사용하는 것이기 때문에 신규 트랜잭션으로 분류되지 않는다.
- 로직2가 수행될 때 커넥션이 필요한 경우 트랜잭션 동기화 매니저로부터 커넥션을 확보해 사용한다.
- 로직2 실행이 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 커밋한다.
- 이 때, 로직2를 실행한 트랜잭션은 외부에서 이어받은 내부 트랜잭션이다. 이 경우 실제 트랜잭션이 아니기 때문에 커밋을 호출하지 않는다.
- 로직1 실행이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋한다.
- 이 때, 로직1을 실행한 트랜잭션은 외부에서 처음 생성된 신규 트랜잭션이기 때문에 DB 커넥션에 실제 커밋을 호출한다.
- 실제 데이터베이스 커밋이 반영되고 물리 트랜잭션도 끝난다.
❗트랜잭션 매니저에 커밋을 호출한다고 해서 실제 데이터베이스 커넥션에 물리 커밋이 발생하는 것이 아니다. ❗신규 트랜잭션인 경우 실제 커넥션을 사용해서 물리 커밋과 롤백을 수행한다. ❗내부 트랜잭션이 외부의 트랜잭션을 이어 받아 사용하게 되면 내부 트랜잭션에서 트랜잭션 매니저에 커밋하는 것이 항상 물리 커밋으로 이어지는 것이 아니다. ❗결과적으로 외부 트랜잭션이 물리 커밋을 담당하게 되고 이 외부 트랜잭션을 내부에서 이어서 추가로 사용하는 내부 트랜잭션의 경우 논리 커밋을 담당한다고 볼 수 있는 것이다.
[ 외부 롤백 ]
- 외부 트랜잭션이든 내부 트랜잭션이든 하나의 물리 트랜잭션 안에 여러 논리 트랜잭션이 포함된 상태에서 외부 트랜잭션에서 롤벡이 발생하게 되면 전체 물리 트랜잭션 역시 롤백이 된다.
- 로직2 실행이 끝나고 트랜잭션 매니저를 통해서 내부 트랜잭션을 커밋한다.
- 이 때, 로직2를 실행하는 트랜잭션은 외부 트랜잭션에서 생성된 신규 트랜잭션을 그대로 이어 받아서 사용한 것이므로 실제 커밋이 호출되지 않는다.
- 로직1 실행이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 롤백한다.
- 이 때, 로직1을 수행하는 트랜잭션은 외부 트랜잭션에서 생성된 신규 트랜잭션이다. 따라서 DB 커넥션에 실제 롤백을 호출한다.
- 트랜잭션 매니저의 롤백 호출이 일어났으므로 이는 논리 롤백에 해당하고 실제 DB 커넥션에 롤백을 호출하는 것은 물리 롤백에 해당한다.
[ 내부 롤백 ]
- 로직2 실행이 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 롤백한다.
- 이 때, 로직2를 수행하는 트랜잭션은 내부 트랜잭션으로 외부 트랜잭션을 이어 받은 기존의 트랜잭션이다.
- 내부 트랜잭션에서 실제 DB 커넥션에 커밋이나 롤백을 호출하면 안 되기 때문에 트랜잭션 동기화 매니저에
rollbackOnly=true
표시를 해준다. - 로직1 실행이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋한다.
- 이 때, 로직1을 수행하는 트랜잭션은 외부 트랜잭션으로 신규 트랜잭션에 해당한다. DB 커넥션에 실제 커밋을 호출해야 하지만 이 때, 먼저 트랜잭션 동기화 매니저에
rollbackOnly=true
표시가 있는지 확인한다. 롤백 전용 표시가 있으면 물리 트랜잭션을 커밋하는 것이 아니라 롤백한다. - 결국 논리 트랜잭션의 롤백 호출이 일어나게 되고 물리 트랜잭션의 롤백으로 이어져 실제 DB 커넥션에 롤백이 반영되고 물리 트랜잭션이 끝나게 된다.
[ REQUIRES_NEW ]
- REQUIRES_NEW 옵션은 외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 별도의 물리 트랜잭션을 사용하는 방법이다. 그래서 이전 방법들과 다르게 커밋과 롤백이 각각 별도로 이루어지게 된다.
TransactionDefinition.PROPAGATION_REQUIRES_NEW
- 물리 트랜잭션을 분리하려면 내부 트랜잭션 시작시 REQUIRES_NEW 옵션을 사용하면 된다.
- 외부 트랜잭션과 내부 트랜잭션이 별도의 물리 트랜잭션을 가진다.
- 위와 달리 DB 커넥션을 따로 사용한다는 뜻이다.
- 최종적으로 로직2의 트랜잭션 롤백이 발생하더라도 로직1의 트랜잭션 커밋은 이루어진다.
- 외부 트랜잭션을 시작한다.
- 생성자를 통해 주입받는 데이터 소스를 통해 커넥션을 생성한다.
- 자동 커밋 모드를 수동 커밋 모드로 변경함으로써 실제 물리 트랜잭션을 시작한다.
- 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다.
- 신규 트랜잭션이기 때문에 TransactionStatus에 신규 트랜잭션 결과를 담게 된다.
- 로직1이 수행되는데 이 때, 커넥션이 필요한 경우 트랜잭션 동기화 매니저에 보관된 커넥션을 획득해서 사용한다.
- REQUIRES_NEW 옵션이기 때문에 외부 트랜잭션을 이어서 사용하지 않고 새롭게 내부 트랜잭션을 시작한다.
- 생성자를 통해 주입받는 데이터 소스를 통해 커넥션을 생성한다.
- 자동 커밋 모드를 수동 커밋 모드로 변경함으로써 실제 물리 트랜잭션을 시작한다.
- 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다.
- 외부 트랜잭션을 이어 받아 사용하는 것이 아니라 새로운 트랜잭션이기 때문에 신규 트랜잭션으로 분류가 된다.
- 로직2가 수행되는데 이 때, 커넥션이 필요한 경우 트랜잭션 동기화 매니저에 보관된 커넥션을 획득해서 사용한다.
- 로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 롤백한다.
- 이 때, REQUIRES_NEW 옵션이기 때문에 외부 트랜잭션을 이어서 사용하지 않는다. 따라서 신규 트랜잭션으로 분류가 되기 때문에 실제 DB 커넥션에 롤백을 호출한다.
- 내부 트랜잭션이 물리 트랜잭션을 롤백한다.
- 외부 트랜잭션에 커밋을 요청한다.
- 이 때, 해당 트랜잭션은 신규 트랜잭션이기 때문에 실제 DB 커넥션에 커밋을 호출한다.
- rollbackOnly 설정이 없기 때문에 커밋이 된다.
- 실제 DB 커넥션에 커밋이 발생한다.
❗하나의 요청에 여러 커넥션을 사용하기 때문에 데드락이 발생할 위험이 높다. ❗데이터 정합성 문제가 발생할 수 있다.
[ 다양한 전파 옵션 ]
- 실제는 대부분 REQUIRED 옵션을 사용하고 아주 가끔 REQUIRES_NEW 옵션을 사용하며 나머지는 거의 사용하지 않는다.
- REQUIRED
- 기존 트랜잭션이 있으면 이어 받고 없으면 새로 생성한다.
- 가장 많이 사용하는 기본 설정
- REQUIRES_NEW
- 항상 새로운 트랜잭션을 생성한다.
- 데드락 발생 위험이 높고 데이터 정합성 불일치가 발생할 수 있다.
- SUPPORT
- 트랜잭션을 지원한다.
- 기존 트랜잭션이 있으면 이어 받고 없으면 없는대로 진행한다.
- 기타 : NOT_SUPPORT, MANDATORY, NEVER, NESTED ...