하나의 트랜잭션이 수행중이고, 아직 끝나지 않았는데 또 다른 트랜잭션이 수행되는 경우가 있다.
먼저 시작한 트랜잭션을 외부 트랜잭션, 그 뒤의 트랜잭션을 내부 트랜잭션이라고 한다.
스프링은 이 경우 하나의 트랜잭션을 만들어준다. (default는 내부 트랜잭션이 외부 트랜잭션에 참여하는 것)
논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶이고, 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위이다.
위의 상황같은 경우 발생되는 개념이고 REQUIRED
전파 옵션을 사용하는 경우에 나타난다.
원칙
@Test
void inner_commit() {
log.info("외부 트랜잭션 시작");
TransactionStatus outer = txManager.getTransaction(new DefaultTransactionDefinition());
log.info("outer.isNewTransaction = {}", outer.isNewTransaction());
log.info("내부 트랜잭션 시작");
TransactionStatus inner = txManager.getTransaction(new DefaultTransactionDefinition());
log.info("inner.isNewTransaction = {}", inner.isNewTransaction());
log.info("내부 트랜잭션 커밋");
txManager.commit(inner);
log.info("외부 트랜잭션 커밋");
txManager.commit(outer);
}
status.isNewTransaction()
이라는 메서드로 외부/ 내부 트랜잭션을 구분할 수 있다.
트랜잭션 참여
외부 트랜잭션과 내부 트랜잭션이 하나의 물리 트랜잭션으로 묶인다고 설명했는데 어떻게 커밋을 2번 수행할까?
하나의 커넥션에 커밋은 한번만 호출할 수 있다.
외부 트랜잭션 시작
Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [HikariProxyConnection@1567441485 wrapping conn0
Switching JDBC Connection [HikariProxyConnection@1567441485 wrapping conn0
outer.isNewTransaction = true
내부 트랜잭션 시작
Participating in existing transaction
inner.isNewTransaction = false
내부 트랜잭션 커밋
외부 트랜잭션 커밋
Initiating transaction commit
Committing JDBC transaction on Connection [HikariProxyConnection@1567441485 wrapping conn0:
Releasing JDBC Connection [HikariProxyConnection@1567441485 wrapping conn0:
외부 트랜잭션이 시작할 때 커넥션을 얻지만 내부 트랜잭션은 생성된 트랜잭션에 참여한다는 로그가 찍힌다.
실행 결과를 보면 외부 트랜잭션을 시작하거나 커밋할 때 DB커넥션을 통한 물리 트랜잭션을 시작하고, DB커넥션을 통해 커밋하는 것을 확인할 수 있다.
외부 트랜잭션에 의해서만 물리 트랜잭션이 동작한다. 만약 내부 트랜잭션의 커밋이 동작한다면 같은 커넥션을 쓰고 있는 외부 트랜잭션까지 이어갈 수 없다.
스프링은 처음 트랜잭션을 시작한 외부 트랜잭션이 실제 물리 트랜잭션을 관리하도록 해 트랜잭션 중복커밋 문제를 해결한다.
-> 트랜잭션에 참여한다는 뜻은 아무것도 하지 않는다는 뜻이다.
트랜잭션의 시작, 커밋or롤백을 수행할 지는 isNewTransaction()의 결과값에 따라 동작이 달라진다.
실제로 커넥션을 사용해 물리 커밋과 롤백을 수행하는 것은 신규 트랜잭션일 경우에 해당된다. 이 개념을 위해 물리 트랜잭션, 논리 트랜잭션을 나눈 것이다.