[Spring DB 2편] 11. 트랜잭션 전파 활용

HJ·2023년 2월 9일
0

Spring DB 2편

목록 보기
11/11
post-custom-banner

김영한 님의 스프링 DB 2편 - 데이터 접근 활용 기술 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard


1. 예제 프로젝트

  • Member / Log : 각각 username, message로 객체 생성

  • MemberRepository / LogRepository : 객체를 DB 저장( save() ) 및 조회( find() )

    • LogRepository.save() : "로그예외"라는 메세지이면 런타임 예외 발생
  • MemberService : String을 받아 Member, Log 객체 생성 및 각 Repository 를 통해 저장




2. 별도 트랜잭션 사용

2-1. 상황

  • MemberService 의 joinV1() 에 @Transactional 이 없는 상황
  • 각 Repository의 save() 메서드에 @Transactional 이 있는 상황


2-2. 둘 다 커밋

  1. MemberService 에서 MemberRepository 를 호출

   ➜ MemberRepository 에 @Transactional 이 있으므로 트랜잭션 AOP가 작동

   ➜ 트랜잭션 매니저에 트랜잭션을 요청하고 데이터 소스를 통해 커넥션 획득

   ➜ 해당 커넥션을 수동 커밋 모드로 변경하고 트랜잭션 시작

   ➜ 트랜잭션 동기화 매니저를 통해 트랜잭션을 시작한 커넥션을 보관

   ➜ 트랜잭션 매니저의 호출 결과로 status 를 반환

  1. MemberRepository 가 회원을 저장할 때 트랜잭션이 시작된 con1 을 사용

  2. MemberRepository 가 정상 응답을 반환 ➜ 트랜잭션 AOP는 트랜잭션 매니저에 커밋 요청

  3. 트랜잭션 매니저는 신규 트랜잭션 여부, rollbackOnly 여부를 모두 체크한 후 con1 을 통해 물리 트랜잭션을 커밋

  4. LogRepository 도 커밋하기 때문에 동일한 과정을 수행


2-3. 하나가 롤백

  1. LogRepository 는 다른 트랜잭션과 관련된 con2 를 사용한다

  2. 로그예외 라는 이름을 전달해서 LogRepository 에 런타임 예외가 발생한다

  3. LogRepository 는 해당 예외를 밖으로 던진다. 이 경우 트랜잭션 AOP가 예외를 받게된다

  4. 런타임 예외가 발생해서 트랜잭션 AOP는 트랜잭션 매니저에 롤백을 호출한다

  5. 트랜잭션 매니저는 신규 트랜잭션이므로 물리 롤백을 호출한다




3. 단일 트랜잭션

3-1. 상황

  • MemberService 의 joinV1() 에 @Transactional 이 있는 상황
  • 각 Repository의 save() 메서드에 @Transactional 이 없는 상황


3-2. 동작 흐름

  • @Transactinal 이 MemberService 에만 있기 때문에 여기에만 트랜잭션 AOP가 적용

    • MemberRepository, LogRepository 는 트랜잭션 AOP 적용 X
  • MemberService 의 시작부터 종료까지 모든 로직을 하나의 트랜잭션으로 묶을 수 있다

    • MemberService 의 시작 이후 모든 관련 로직은 해당 트랜잭션이 생성한 커넥션 사용

    • MemberRepository, LogRepository를 호출하기 때문에 같은 트랜잭션 사용




4. 트랜잭션 전파

4-1. 전파 필요성

  • MemberService 부터 MemberRepository , LogRepository 를 모두 하나의 트랜잭션으로 묶고 싶은 클라이언트

  • MemberRepository 만 호출하고 여기에만 트랜잭션을 사용하고 싶은 클라이언트

  • LogRepository 만 호출하고 여기에만 트랜잭션을 사용하고 싶은 클라이언트가 존재할 때

  • MemberService 에만 트랜잭션을 적용하면 다른 클라이언트가 호출하는 Repository 에는 트랜잭션을 적용할 수 없는 문제가 발생

  • 이런 문제를 해결하기 위해 트랜잭션 전파가 필요


4-2. 해결방법

  • @Transactional 이 적용되어 있으면 기본으로 REQUIRED 전파 옵션을 사용

  • 이 옵션은 기존 트랜잭션이 없으면 새로 생성하고, 있으면 기존 트랜잭션에 참여한다

  • 참여한다는 의미는 같은 동기화 커넥션을 사용한다는 것이다

  • 즉, MemberService, MemberRepository, LogRepository 모두 @Transactional을 사용하면 해결할 수 있다


4-3. 커밋 동작 흐름

  • 모두 @Transactional 이 붙어 있기 때문에 트랜잭션 AOP가 적용
  1. 클라이언트가 MemberService 를 호출하면서 트랜잭션 AOP가 호출

   ➜ 신규 트랜잭션 생성, 물리 트랜잭션 시작

  1. MemberRepository 를 호출하면서 트랜잭션 AOP가 호출

   ➜ 기존 트랜잭션이 있으므로 참여한다

  1. MemberRepository 의 로직 호출이 끝나고 정상 응답하면 트랜잭션 AOP가 호출

   ➜ 트랜잭션 AOP는 정상 응답이므로 트랜잭션 매니저에 커밋을 요청

   ➜ 신규 트랜잭션이 아니므로 실제 커밋을 호출하지 않는다

  1. LogRepositoryMemberRepository 와 동일하게 수행된다

  2. MemberService 의 로직 호출이 끝나고 정상 응답하면 트랜잭션 AOP가 호출

   ➜ 트랜잭션 AOP는 정상 응답이므로 트랜잭션 매니저에 커밋을 요청

   ➜ 신규 트랜잭션이므로 물리 커밋을 호출


4-4. 롤백 동작 흐름

  1. 클라이언트가 MemberService 를 호출하면서 트랜잭션 AOP가 호출

   ➜ 신규 트랜잭션 생성, 물리 트랜잭션 시작

  1. MemberRepository 를 호출하면서 트랜잭션 AOP가 호출

   ➜ 기존 트랜잭션이 있으므로 참여한다

  1. MemberRepository 의 로직 호출이 끝나고 정상 응답하면 트랜잭션 AOP가 호출

   ➜ 트랜잭션 AOP는 정상 응답이므로 트랜잭션 매니저에 커밋을 요청

   ➜ 신규 트랜잭션이 아니므로 실제 커밋을 호출하지 않는다


  1. LogRepository 를 호출하면서 트랜잭션 AOP가 호출

   ➜ 기존 트랜잭션이 있으므로 참여한다

  1. LogRepository 로직에서 런타임 예외가 발생, 트랜잭션 AOP가 예외를 받는다

   ➜ 트랜잭션 AOP는 런타임 예외가 발생했으므로 트랜잭션 매니저에 롤백을 요청

   ➜ 신규 트랜잭션이 아니므로 물리 롤백을 호출하지 않고 rollbackOnly 를 설정

   ➜ LogRepository 가 예외를 던졌기 때문에 트랜잭션 AOP도 해당 예외를 밖으로 던진다


  1. MemberService 에서도 런타임 예외를 받게 되는데, 처리하지 않고 밖으로 던진다

   ➜ 트랜잭션 AOP는 런타임 예외가 발생했으므로 트랜잭션 매니저에 롤백을 요청

   ➜ 신규 트랜잭션이므로 물리 롤백을 호출

   ➜ MemberService 가 예외를 던졌기 때문에 트랜잭션 AOP도 해당 예외를 밖으로 던진다

   ➜ 클라이언트가 LogRepository 부터 넘어온 런타임 예외를 받게 된다




5. 복구 REQUIRED

5-1. 상황

  • 로그를 DB에 저장할 때 문제가 발생하면 전체 트랜잭션이 롤백되기 때문에 회원 가입 실패

  • but> 로그를 DB에 저장하는 것은 실패해도 회원 가입은 정상적으로 진행되도록 하고 싶다

  • MemberService 에서 예외를 잡아서 정상 흐름을 반환하는 joinV2() 를 사용하면 회원 가입은 정상적으로 진행될 것인가?


5-2. 동작 확인

  • 동작을 확인해보면 회원 가입이 정상적으로 이루어지지 않고, MemberService 에서 클라이언트로 UnexpectedRollbackException 을 던지게 된다

  • 내부 트랜잭션에서 rollbackOnly 를 설정하기 때문에 정상 흐름 처리를 해서 외부 트랜잭션에서 커밋을 해도 물리 트랜잭션이 롤백되기 때문이다


5-3. 동작 흐름

  1. LogRepository 에서 예외가 발생하고 예외를 던진다

   ➜ LogRepository 의 트랜잭션 AOP가 해당 예외를 받는다

   ➜ 신규 트랜잭션이 아니므로 물리 트랜잭션을 롤백하지는 않는다

   ➜ 트랜잭션 동기화 매니저에 rollbackOnly 를 표시한다

   ➜ 그 후 트랜잭션 AOP는 예외를 밖으로 던진다


  1. MemberService 는 던져진 예외를 복구하고 정상 흐름을 반환

   ➜ 정상 흐름이 되었으므로 MemberService 의 트랜잭션 AOP는 커밋을 호출

   ➜ 신규 트랜잭션이므로 물리 트랜잭션을 커밋해야하는데 이 때 rollbackOnly 를 체크

   rollbackOnly 가 체크 되어 있으므로 물리 트랜잭션을 롤백한다

   ➜ 트랜잭션 매니저는 UnexpectedRollbackException 예외를 던진다

   ➜ 트랜잭션 AOP도 전달받은 UnexpectedRollbackException 을 클라이언트에 던진다


5-4. 정리

  • 논리 트랜잭션 중 하나라도 롤백되면 전체 트랜잭션은 롤백된다

  • 내부 트랜잭션이 롤백 되었는데, 외부 트랜잭션이 커밋되면 UnexpectedRollbackException 예외가 발생

  • rollbackOnly 상황에서 커밋이 발생하면 UnexpectedRollbackException 예외가 발생




6. 복구 REQUIRES_NEW

6-1. 새로운 해결책

  • MemberService 에서 정상 흐름을 반환하는 joinV2() 를 사용해도 로그를 남길 때 예외가 발생하면 전체 트랜잭션은 롤백되어 버렸다

  • joinV2() 를 사용하면서 LogRepository.save()@Transacional 의 옵션에 REQUIRES_NEW 를 사용하면 로그를 남기는데 실패해도 회원 가입은 정상적으로 저장된다

  • REQUIRES_NEW 는 항상 새로운 트랜잭션을 생성

   ➜ 물리 트랜잭션 자체가 분리되어 기존 트랜잭션에 영향을 주지 않는다

   ➜ 롤백해도 rollbackOnly 표시가 되지 않고 해당 트랜잭션이 롤백되고 종료

   ➜ 즉, 로그 남기는 것과 관계 없이 회원 가입이 정상적으로 처리되었으면 회원이 저장된다


6-2. 동작 흐름

  1. LogRepository 에서 예외가 발생하고 예외를 던진다

   ➜ LogRepository 의 트랜잭션 AOP가 해당 예외를 받는다

   ➜ 신규 트랜잭션이므로 물리 트랜잭션을 롤백한다

   ➜ 물리 트랜잭션을 롤백했으니 rollbackOnly 표시 없이 종료된다

   ➜ 그 후 트랜잭션 AOP는 전달받은 예외를 밖으로 던진다


  1. MemberService 는 던져진 예외를 복구하고 정상 흐름을 반환

   ➜ 정상 흐름이 되었으므로 MemberService 의 트랜잭션 AOP는 커밋을 호출

   ➜ 신규 트랜잭션이므로 물리 트랜잭션을 커밋해야하는데 이 때 rollbackOnly 를 체크

   rollbackOnly 가 없으므로 물리 트랜잭션을 커밋하고 정상 흐름이 반환


6-3. 참고

  • REQUIRES_NEW 를 사용하면 하나의 HTTP 요청에 동시에 2개의 데이터베이스 커넥션을 사용하게 된다

  • 새로운 커넥션이 사용될 때 이전의 커넥션은 잠시 중단된 상태로 유지된다

post-custom-banner

0개의 댓글