Spring ‐ 스프링 트랜잭션 - dnwls16071/Backend_Study_TIL GitHub Wiki

📚 트랜잭션 추상화

image

  • platformTransactionManager 인터페이스는 트랜잭션 매니저라고 불리는데, 트랜잭션 시작, 종료, 커밋, 롤백에 관한 내용이 있고 이에 대한 각 접근 기술에 대한 구현체를 제공한다.
  • 따라서 비즈니스 로직은 스프링 트랜잭션 추상화 인터페이스에 의존하게 하면서 전환시 구현체만 갈아껴서 사용하면 된다.

📚 트랜잭션 매니저와 트랜잭션 동기화 매니저

  • 하나의 서비스 로직에서 리포지토리로 접근하는 요청이 있을 때 여러 트랜잭션을 사용하는 것이 아니라 같은 트랜잭션을 사용한다.
  • 이를 위해 스프링은 쓰레드 로컬을 사용해 커넥션을 동기화해주는 트랜잭션 동기화 매니저를 제공한다.

image

  1. 서비스 계층에서 TransactionManager.getTransaction()를 호출해 트랜잭션을 시작한다.
  2. 허나 이 트랜잭션을 시작하려면 커넥션이 필요하다. 트랜잭션 매니저는 생성자 인자로 주입받는 데이터 소스를 통해 커넥션을 만들고 트랜잭션을 시작한다.
  3. setAutoCommit(false)는 자동 커밋 대신 수동 커밋을 하기 위함이고 수동 커밋을 하게 됨으로써 실제 데이터베이스 트랜잭션을 시작한다.
  4. 만들어진 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  5. 트랜잭션 동기화 매니저는 커넥션을 쓰레드 로컬에 보관하기 때문에 멀티 쓰레드 환경에서 안전하다.

image

  1. 서비스는 비즈니스 로직을 실행하면서 리포지토리 메서드들을 호출한다.
  2. 리포지토리 메서드의 경우 트랜잭션이 시작된 커넥션을 필요로 한다. 리포지토리는 DataSource.getConnection()을 통해 트랜잭션 동기화 매니저에 보관된 커넥션을 사용한다.
  3. 획득한 커넥션을 사용해서 SQL을 데이터베이스에 전달해서 데이터 접근을 하게 된다.

image

  1. 비즈니스 로직이 끝나고 트랜잭션을 종료한다.
  2. 트랜잭션을 종료하려면 트랜잭션 동기화 매니저에 보관된 커넥션을 획득한다.
  3. 획득한 커넥션을 통해 데이터베이스에 트랜잭션 커밋 혹은 롤백을 수행한다.
  4. 전체 리소스를 정리한다.
  • 트랜잭션 동기화 매니저(쓰레드 로컬 정리)
  • 수동 커밋을 자동 커밋으로 변경(커넥션 풀을 고려)
  • 커넥션 종료(커넥션을 사용한 경우에는 커넥션 풀에 반환까지도)

📚 스프링 AOP를 이용한 트랜잭션

image

  • 프록시를 사용하여 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리한다.
// 프록시 객체
public class TransactionProxy {
    
    private MemberService target;

    public void logic() { 
        // 트랜잭션 시작
        TransactionStatus status = transactionManager.getTransaction(..);
        try {            
            target.logic(); // 실제 대상 호출
            transactionManager.commit(status); // 성공시 커밋 
        } catch (Exception e) {
            transactionManager.rollback(status); // 실패시 롤백
            throw new IllegalStateException(e);
        }
    }
}
  • 프록시 객체는 트랜잭션 작업을 처리하고 중간에 서비스 로직을 호출하는 역할을 한다.
  • 비즈니스 로직 처리와 트랜잭션 처리를 분리를 프록시를 통해 수행함으로써 서비스 계층에 트랜잭션에 대한 코드를 작성하지 않아도 된다.
  • 이 명확한 분리는 @Transactional 어노테이션이 제공한다.
@Slf4j
@Service
@RequiredArgsConstructor
public class MemberServiceV3_3 {

    private final MemberRepositoryV3 memberRepository;

    @Transactional
    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        bizLogic(fromId, toId, money);
    }

    ... 생략
}

image

  1. 클라이언트로부터 요청이 들어오면 프록시가 호출된다.
  2. 스프링 컨테이너에서 데이터 소스, 트랜잭션 매니저를 스프링 빈으로 관리하며 트랜잭션 매니저를 획득한다.
  3. 트랜잭션 매니저는 데이터 소스를 생성자 인자로 주입받아 트랜잭션을 시작한다.
  4. 데이터 소스를 통해 커넥션을 생성한다.
  5. 만든 커넥션을 수동 커밋으로 변경한다.
  6. (4) 과정을 통해서 생성된 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  7. 보관된 커넥션은 쓰레드 로컬에 의해 보관된다.
  8. 프록시(트랜잭션 처리 담당)가 아닌 비즈니스 로직을 수행하는 서비스를 호출한다.
  9. 리포지토리에서 트랜잭션 동기화 매니저에 보관된 커넥션을 획득해 데이터 접근 로직을 수행한다.
  10. 트랜잭션 처리 로직으로 돌아와 성공하면 커밋하고 실패하면 롤백을 한다. 이후 트랜잭션을 종료한다.

❓선언적 트랜잭션 관리 vs 프로그래밍 방식 트랜잭션 관리

  • @Transactional 어노테이션 하나만 선언해서 사용하는 트랜잭션을 선언적 트랜잭션이라고 한다.
  • 트랜잭션 매니저, 트랜잭션 템플릿을 사용해서 트랜잭션 관련 코드를 직접 작성해서 사용하는 트랜잭션을 프로그래밍 방식 트랜잭션이라고 한다.
  • 스프링 부트를 사용하면 데이터 소스를 자동 등록할 수 있다.
  • 데이터 소스 자동 등록 - application.properties / application.yml