Spring/[인프런 김영한 스프링 DB 2편 - 데이터 접근 활용 기술]
[인프런 김영한 스프링 DB 2편 - 데이터 접근 활용 기술] 스프링 트랜잭션 전파 - 기본
h2boom
2024. 11. 29. 23:16
스프링 트랜잭션 전파
스프링 트랜잭션 - 커밋, 롤백
- 여러 트랜잭션을 각각 따로 사용하는 경우
- 트랜잭션이 각각 수행되면서 사용되는 커넥션도 각각 다르다.
- 커넥션 풀이 있는 경우 커넥션을 사용하고 반납하다보면 물리적으로는 같은 커넥션을 사용할 수는 있지만 완전히 다른 커넥션으로 취급된다.
- 윗 그림은 커넥션 풀을 사용하지 않는다고 가정했을 때 동작 방식
- 이 경우 트랜잭션을 각자 관리하기 때문에 전체 트랜잭션을 묶을 수 없다.
- 트랜잭션이 각각 수행되면서 사용되는 커넥션도 각각 다르다.
스프링 트랜잭션 - 전파
- 트랜잭션 전파(propagation) : 어떤 트랜잭션이 동작중인 과정에서 다른 트랜잭션을 실행할 경우 어떻게 동작할지 결정하는 것.
- 트랜잭션 전파의 기본 옵션인 REQUIRED를 기준으로 아래 내용 작성
- 외부 트랜잭션이 수행중일 때 내부 트랜잭션이 추가로 수행되는 경우
- 스프링은 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 트랜잭션으로 만들어주는 방식( = 내부 트랜잭션이 외부 트랜잭션에 참여하는 것)이 기본 동작이며 옵션을 통해 다른 동작 선택 가능
- 스프링은 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 트랜잭션으로 만들어주는 방식( = 내부 트랜잭션이 외부 트랜잭션에 참여하는 것)이 기본 동작이며 옵션을 통해 다른 동작 선택 가능
- 물리 트랜잭션 vs 논리 트랜잭션
- 스프링에서는 논리 트랜잭션과 물리 트랜잭션으로 개념을 나눈다.
- 논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶인다.
- 물리 트랜잭션 : 실제 DB에 적용되는 트랜잭션을 의미한다.
- 실제 커넥션을 통해 커밋, 롤백하는 단위
- 논리 트랜잭션 : 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위
- 트랜잭션이 진행되는 중에 내부에 추가로 트랜잭션을 사용하는 경우에 논리 트랜잭션 개념이 나타난다.
- 단순히 트랜잭션이 하나인 경우에는 둘을 구분하지 않는다.
- 트랜잭션 원칙
- 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
- 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.
- => 모든 트랜잭션 매니저를 커밋해야 물리 트랜잭션이 커밋된다.
하나의 트랜잭션 매니저라도 롤백하면 물리 트랜잭션은 롤백된다.
- 외부 트랜잭션과 내부 트랜잭션 모두 커밋되는 경우
@Test
public void inner_commit() {
log.info("외부 트랜잭션 시작");
TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
log.info("outer.isNewTransaction()={}", outer.isNewTransaction());
log.info("내부 트랜잭션 시작");
TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
log.info("inner.isNewTransaction()={}", inner.isNewTransaction());
log.info("내부 트랜잭션 커밋");
txManager.commit(inner);
log.info("외부 트랜잭션 커밋");
txManager.commit(outer);
}
- 외부, 내부 트랜잭션이 모두 커밋인 경우 예제
- 외부 트랜잭션 outer가 수행중일 때 내부 트랜잭션 inner를 추가로 수행
- 외부 트랜잭션이 처음 수행된 트랜잭션이기에 isNewTransaction = true가 된다.
- 내부 트랜잭션을 시작하는 시점에는 외부 트랜잭션이 이미 진행중이기에 내부 트랜잭션은 외부 트랜잭션에 참여한다.
- 이 경우 신규 트랜잭션이 아니기에 isNewTransaction = false가 된다.
- 트랜잭션 참여 : 내부 트랜잭션이 외부 트랜잭션을 그대로 이어 받아서 따른다는 의미
=> 외부 트랜잭션의 범위가 내부 트랜잭션까지 넓어진다는 의미
- 외부 트랜잭션과 내부 트랜잭션이 하나의 물리 트랜잭션으로 묶이는 것.
외부 트랜잭션 시작
Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [HikariProxyConnection@1943867171 wrapping conn0] for JDBC transaction
Switching JDBC Connection [HikariProxyConnection@1943867171 wrapping conn0] to manual commit
outer.isNewTransaction()=true
내부 트랜잭션 시작
Participating in existing transaction
inner.isNewTransaction()=false
내부 트랜잭션 커밋
외부 트랜잭션 커밋
Initiating transaction commit
Committing JDBC transaction on Connection [HikariProxyConnection@1943867171
wrapping conn0]Releasing JDBC Connection [HikariProxyConnection@1943867171 wrapping conn0] after transaction
- 테스트 코드 실행 결과
- 내부 트랜잭션을 시작할 때 메시지 Participating in existing transaction => 트랜잭션이 기존에 존재하는 외부 트랜잭션에 참여한다는 의미
- 외부 트랜잭션을 시작하거나 커밋할 때는 DB 커넥션을 통한 물리 트랜잭션을 시작(manual commit)하고 DB 커넥션을 통해 커밋을 한다.
- 내부 트랜잭션을 시작하거나 커밋할 때는 DB 커넥션을 통해 커밋하는 로그가 전혀 없다.
- 외부 트랜잭션만 물리 트랜잭션을 시작하고 커밋한다.
- 한 커넥션에서 커밋 / 롤백을 한 번 밖에 못하기에 내부 트랜잭션에서 커밋하면 트랜잭션이 끝나버리기 때문에 처음 시작한 외부 트랜잭션까지 이어갈 수 없기에 내부 트랜잭션은 커넥션을 통한 물리 트랜잭션을 커밋하면 안된다.
- 스프링에서는 여러 트랜잭션이 함께 사용되는 경우 처음 트랜잭션을 시작한 외부 트랜잭션이 실제 물리 트랜잭션을 관리하도록 함으로 트랜잭션 중복 커밋 문제를 해결한다.
- 요청 흐름 - 외부 트랜잭션
- 1.getTransaction() 호출로 외부 트랜잭션 시작
- 2.트랜잭션 매니저는 데이터 소스를 통해 커넥션 생성
- 3.생성한 커넥션을 수동 커밋 모드로 설정 -> 물리 트랜잭션 시작
- 4.트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션 보관
- 5.트랜잭션 매니저는 트랜잭션을 생성한 결과를 TransactionStatus에 담아서 보관하며 신규 트랜잭션 여부가 true로 담겨있다.
- 6.로직1이 수행되고 커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 트랜잭션이 적용된 커넥션을 획득해서 사용
- 요청 흐름 - 내부 트랜잭션
- 7.getTransaction() 호출로 내부 트랜잭션 시작
- 8.트랜잭션 매니저는 트랜잭션 동기화 매니저를 통해 기존 트랜잭션이 존재하는지 확인
- 9.기존 트랜잭션이 존재하므로 기존 트랜잭션에 참여
= 아무것도 하지 않는다는 의미- 물리 트랜잭션이 진행중이므로 이후 로직에서 트랜잭션 동기화 매니저에 보관된 기존에 시작된 트랜잭션을 자연스럽게 사용하게 된다.
- 10.트랜잭션 매니저는 트랜잭션을 생성한 결과를 TransactionStatus에 담아서 반환하며 신규 트랜잭션의 여부는 false이다.
- 11.로직2가 수행되고 커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 외부 트랜잭션이 보관한 커넥션을 획득해서 사용한다.
- 응답 흐름 - 내부 트랜잭션
- 12.로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 커밋
- 13.트랜잭션 매니저는커밋 시점에 신규 트랜잭션 여부에 따라 다르게 동작
- 내부 트랜잭션은 신규 트랜잭션이 아니기에 실제 커밋을 호출하지 않는다.
- 실제 커넥션에 커밋이나 롤백을 호출하면 물리 트랜잭션이 종료되므로 아직 트랜잭션이 끝나지 않았기에 실제 커밋을 호출하면 안되며 외부 트랜잭션이 종료될 때까지 이어져야 한다.
- 내부 트랜잭션은 신규 트랜잭션이 아니기에 실제 커밋을 호출하지 않는다.
- 응답 흐름 - 외부 트랜잭션
- 14.로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋
- 15.트랜잭션 매니저는 커밋 시점에 신규 트랜잭션 여부에 따라 다르게 동작
- 외부 트랜잭션은 신규 트랜잭션이기에 DB 커넥션에 실제 커밋을 호출한다.
- 트랜잭션 매니저에 커밋하는 것이 논리적인 커밋이라면 실제 커넥션에 커밋하는 것은 물리적인 커밋으로 볼 수 있다.
- 16.실제 DB에 커밋이 반영되고 물리 트랜잭션도 종료된다.
- 트랜잭션 매니저에 커밋을 호출하더라도 항상 실제 커넥션에 물리 커밋이 발생하지 않는다.
- 신규 트랜잭션의 경우에만 실제 커넥션을 사용해서 물리 커밋과 롤백을 수행한다.
신규 트랜잭션이 아니면 실제 커넥션을 사용하지 않는다.
- 신규 트랜잭션의 경우에만 실제 커넥션을 사용해서 물리 커밋과 롤백을 수행한다.
스프링 트랜잭션 전파 - 외부 롤백
- 내부 트랜잭션은 커밋되고 외부 트랜잭션이 롤백되는 경우
- 외부 트랜잭션이 물리 트랜잭션을 시작하고 롤백한다.
- 내부 트랜잭션은 물리 트랜잭션에 직접 관여하지 않는다.
- 요청 흐름은 둘 다 커밋하는 예제와 동일
- 응답 흐름 - 내부 트랜잭션
- 1.로직2가 끝난 후 트랜잭션 매니저를 통해 내부 트랜잭션을 커밋
- 2.신규 트랜잭션이 아니기에 트랜잭션 매니저는 실제 커넥션의 커밋을 호출하지 않는다.
- 응답 흐름 - 외부 트랜잭션
- 3.로직1이 끝난 후 트랜잭션 매니저를 통해 외부 트랜잭션을 롤백
- 4.신규 트랜잭션이기에 트랜잭션 매니저는 실제 커넥션의 롤백을 호출한다.
- 5.실제 DB에 롤백이 반영되고 물리 트랜잭션이 종료된다.
스프링 트랜잭션 전파 - 내부 롤백
- 내부 트랜잭션은 물리 트랜잭션에 영향을 주지 않는다.
- 내부 트랜잭션은 롤백되고 외부 트랜잭션은 커밋이 될 때 전체를 롤백하려면 어떻게 해야할까?
- 내부 트랜잭션은 롤백되고 외부 트랜잭션은 커밋되는 경우
- 내부 트랜잭션 롤백 -> 실제 물리 트랜잭션은 롤백되지 않지만 기존 트랜잭션을 롤백 전용으로 표시한다.
- Participating transaction failed - marking existing transaction as rollback-only
- 외부 트랜잭션 커밋 -> 커밋을 호출했지만 전체 트랜잭션이 롤백 전용으로 표시되어 있기에 물리 트랜잭션을 롤백한다.
- Global transaction is marked as rollback-only but transactional code requested commit
- 내부 트랜잭션 롤백 -> 실제 물리 트랜잭션은 롤백되지 않지만 기존 트랜잭션을 롤백 전용으로 표시한다.
- 요청 흐름은 이전 예제들과 동일
- 응답 흐름 - 내부 트랜잭션
- 1.로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 롤백
- 2.신규 트랜잭션이 아니기에 실제 커넥션의 롤백을 호출하지 않는다.
- 3.내부 트랜잭션은 물리 트랜잭션을 롤백하지 않는 대신 트랜잭션 동기화 매니저에 rollbackOnly=true 라는 표시를 해둔다.
- 응답 흐름 - 외부 트랜잭션
- 4.로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋
- 5.신규 트랜잭션이기에 실제 커넥션의 커밋을 호출한다.
- 실제 커밋 호출 전 트랜잭션 동기화 매니저에 롤백 전용(rollbackOnly=true) 표시가 있는지 확인하고 표시가 있는 경우 물리 트랜잭션을 커밋하는 것이 아닌 롤백을 한다.
- 6.실제 DB에 롤백이 반영되고 물리 트랜잭션이 종료된다.
- 7.트랜잭션 매니저에 커밋을 호출한 개발자 입장에 롤백 전용(rollbackOnly=true)표시로 인해 롤백이 된 상황이므로 UnexpectedRollbackException 런타임 예외를 던진다.
- 커밋을 시도했지만 기대하지 않은 롤백이 발생했다는 것을 명확히 알려주기 위함
- 논리 트랜잭션이 하나라도 롤백되면 물리 트랜잭션은 롤백된다.
- 내부 논리 트랜잭션이 롤백되면 롤백 전용 마크를 표시한다.
- 외부 트랜잭션 커밋 시 롤백 전용 마크를 확인하고 있으면 물리 트랜잭션을 롤백하고 예외를 던진다.
스프링 트랜잭션 전파 - REQUIRES_NEW
- REQUIRES_NEW : 외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 각각 별도의 물리 트랜잭션을 사용하는 방법
- 커밋과 롤백이 각각 별도로 이뤄진다.
- 물리 트랜잭션을 분리하기 위해 내부 트랜잭션 시작 시 REQUIRES_NEW 옵션 사용
- 별도의 물리 트랜잭션을 가지는 것은 DB 커넥션을 따로 사용하는 것
@Test
public void inner_rollback_requires_new() {
log.info("외부 트랜잭션 시작");
TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
log.info("outer.isNewTransaction()={}", outer.isNewTransaction());
log.info("내부 트랜잭션 시작");
DefaultTransactionAttribute definition = new DefaultTransactionAttribute();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus inner = txManager.getTransaction(definition);
log.info("inner.isNewTransaction()={}", inner.isNewTransaction());
log.info("내부 트랜잭션 롤백");
txManager.rollback(inner); //롤백
log.info("외부 트랜잭션 커밋");
txManager.commit(outer); //커밋
}
- REQUIRES_NEW 예제 코드
- 외부 트랜잭션 시작 -> 새 커넥션 획득, 물리 트랜잭션 시작(신규 트랜잭션O)
- 내부 트랜잭션 REQUIRES_NEW 옵션으로 시작 -> 새 커넥션 획득, 물리 트랜잭션 시작(신규 트랜잭션O)
- 내부 트랜잭션 롤백 -> 신규 트랜잭션이므로 물리 트랜잭션 롤백 수행
- 외부 트랜잭션 커밋 -> 신규 트랜잭션이므로 물리 트랜잭션 커밋 수행
- 요청 흐름 - 외부 트랜잭션
- 1.getTransaction() 호출로 외부 트랜잭션 시작
- 2.트랜잭션 매니저는 데이터 소스를 통해 커넥션 con1 생성
- 3.생성한 커넥션을 수동 커밋 모드로 설정 -> 물리 트랜잭션 시작
- 4.트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션 보관
- 5.트랜잭션 매니저는 트랜잭션을 생성한 결과를 TransactionStatus에 담아서 보관하며 신규 트랜잭션 여부가 true로 담겨있다.
- 6.로직1이 수행되고 커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 트랜잭션이 적용된 커넥션을 획득해서 사용
- 요청 흐름 - 내부 트랜잭션
- 7.REQUIRES_NEW 옵션과 함께 getTransaction() 호출로 내부 트랜잭션 시작
- 트랜잭션 매니저는 REQUIRES_NEW 옵션을 확인하고 기존 트랜잭션에 참여하는 것이 아닌 새 트랜잭션을 시작
- 8.트랜잭션 매니저는 데이터 소스를 통해 커넥션 con2 생성
- 9. 생성한 커넥션을 수동 커밋 모드로 설정 -> 물리 트랜잭션 시작
- 10. 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션 보관
- 기존 외부 트랜잭션이 사용하는 con1 커넥션은 잠시 보류되고 내부 트랜잭션의 con2 커넥션이 사용된다.
- 11. 트랜잭션 매니저는 트랜잭션을 생성한 결과를 TransactionStatus에 담아서 보관하며 신규 트랜잭션 여부가 true로 담겨있다.
- 12. 로직2가 수행되고 커넥션이 필요한 경우 트랜잭션 동기화 매니저에 있는 con2 커넥션을 획득해서 사용한다.
- 7.REQUIRES_NEW 옵션과 함께 getTransaction() 호출로 내부 트랜잭션 시작
- 응답 흐름 - 내부 트랜잭션
- 1.로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 롤백
- 2.신규 트랜잭션이기에 트랜잭션 매니저는 실제 con2 커넥션의 물리 트랜잭션을 롤백한다.
- 트랜잭션은 종료되고 con2 커넥션은 종료되거나 커넥션 풀에 반납된다.
- con1 커넥션의 보류가 끝나고 다시 사용한다.
- 응답 흐름 - 외부 트랜잭션
- 1.로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋
- 2.신규 트랜잭션이기에 트랜잭션 매니저는 실제 con1 커넥션의 물리 트랜잭션을 커밋한다.
- rollbackOnly 설정을 체크하지만 없으므로 커밋
- 트랜잭션은 종료되고 con1 커넥션은 종료되거나 커넥션 풀에 반납된다.
- REQUIRES_NEW 옵션을 사용하면 물리 트랜잭션이 명확하게 분리된다.
- DB 커넥션이 동시에 여러 개 사용될 수 있기에 조심할 것
스프링 트랜잭션 전파 - 전파 옵션
- 전파 옵션을 별도로 설정하지 않으면 REQUIRED 옵션을 사용한다.
- 실무에서 대부분 REQUIRED 옵션을 사용
- 트랜잭션 전파 옵션
- REQUIRED : 가장 많이 사용하는 기본 설정으로 기존 트랜잭션이 없으면 생성하고 있으면 참여한다.
- REQUIRES_NEW : 항상 새로운 트랜잭션을 생성한다.
- SUPPORT : 트랜잭션을 지원한다는 의미로 기존 트랜잭션이 없으면 없는대로 진행하고 있으면 참여한다.
- NOT_SUPPORT : 트랜잭션을 지원하지 않는다는 의미로 트랜잭션 없이 진행하며 기존 트랜잭션이 있더라도 보류한다.
- MANDATORY : 의무 사항으로 트랜잭션이 반드시 있어야 하며 기존 트랜잭션이 없으면 예외가 발생한다.
- NEVER : 트랜잭션을 사용하지 않는다는 의미로 기존 트랜잭션이 있으면 예외가 발생한다.
기존 트랜잭션도 허용하지 않는다는 강한 부정의 의미 - NESTED : 기존 트랜잭션이 없으면 트랜잭션을 생성하고 있으면 중첩 트랜잭션을 만든다.
- 중첩 트랜잭션은 외부 트랜잭션의 영향을 받지만 외부 트랜잭션에게 영향을 주지는 못한다.
- ex) 중첩 트랜잭션이 롤백되어도 외부 트랜잭션 커밋 가능
외부 트랜잭션이 롤백되면 중첩 트랜잭션도 롤백된다. - JDBC savepoint 기능을 사용, JPA에서는 중첩 트랜잭션을 사용할 수 없다.
- isolation, timeout, readOnly는 트랜잭션 처음 시작 시에만 적용되기에 트랜잭션이 참여하는 경우에는 적용되지 않는다.
정리
- 트랜잭션 전파(propagation) : 어떤 트랜잭션이 동작중인 과정에서 다른 트랜잭션을 실행할 경우 어떻게 동작할지 결정하는 것.
- 물리 트랜잭션 vs 논리 트랜잭션
- 논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶인다.
- 물리 트랜잭션 : 실제 DB에 적용되는 트랜잭션을 의미한다.
- 실제 커넥션을 통해 커밋, 롤백하는 단위
- 논리 트랜잭션 : 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위
- 트랜잭션이 진행되는 중에 내부에 추가로 트랜잭션을 사용하는 경우에 논리 트랜잭션 개념이 나타난다.
- 트랜잭션 원칙
- 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
- 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.
- => 모든 트랜잭션 매니저를 커밋해야 물리 트랜잭션이 커밋된다.
하나의 트랜잭션 매니저라도 롤백하면 물리 트랜잭션은 롤백된다.
- 외부 트랜잭션만 물리 트랜잭션을 시작하고 커밋한다.
- 한 커넥션에서 커밋 / 롤백을 한 번 밖에 못하기에 내부 트랜잭션은 커넥션을 통한 물리 트랜잭션을 커밋하면 안된다.
- 트랜잭션 롤백 / 커밋되는 경우
- 내부, 외부 트랜잭션 모두 커밋되는 경우
=>물리 트랜잭션 커밋 - 내부 트랜잭션 커밋, 외부 트랜잭션 롤백되는 경우
=> 물리 트랜잭션은 외부 트랜잭션에 의해 결정되므로 롤백 - 내부 트랜잭션 롤백, 외부 트랜잭션 커밋되는 경우
=> 내부 트랜잭션 롤백 시 트랜잭션 동기화 매니저에 롤백 전용(rollbackOnly = true) 표시를 함으로 물리 트랜잭션이 롤백되고 예외 발생
- 내부, 외부 트랜잭션 모두 커밋되는 경우
- REQUIRES_NEW : 외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 각각 별도의 물리 트랜잭션을 사용하는 방법
- REQUIRES_NEW 옵션을 지정하고 내부 트랜잭션 시작하면 각각 별도의 커넥션과 물리 트랜잭션을 사용하고 서로에게 영향을 미치지 않는다.
- REQUIRES_NEW 옵션을 사용하면 여러 개의 커넥션이 동시에 사용될 수 있기에 조심할 것
- 스프링 트랜잭션 전파 옵션은 REQUIRED가 기본 옵션이며 가장 많이 사용하는 옵션이다.
출처 : [인프런 김영한 스프링 DB 2편 - 데이터 접근 활용 기술]
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard
스프링 DB 2편 - 데이터 접근 활용 기술 강의 | 김영한 - 인프런
김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔드
www.inflearn.com