Spring ‐ 스프링 트랜잭션 - dnwls16071/Backend_Study_TIL GitHub Wiki
📚 트랜잭션 추상화
- platformTransactionManager 인터페이스는 트랜잭션 매니저라고 불리는데, 트랜잭션 시작, 종료, 커밋, 롤백에 관한 내용이 있고 이에 대한 각 접근 기술에 대한 구현체를 제공한다.
- 따라서 비즈니스 로직은 스프링 트랜잭션 추상화 인터페이스에 의존하게 하면서 전환시 구현체만 갈아껴서 사용하면 된다.
📚 트랜잭션 매니저와 트랜잭션 동기화 매니저
- 하나의 서비스 로직에서 리포지토리로 접근하는 요청이 있을 때 여러 트랜잭션을 사용하는 것이 아니라 같은 트랜잭션을 사용한다.
- 이를 위해 스프링은 쓰레드 로컬을 사용해 커넥션을 동기화해주는 트랜잭션 동기화 매니저를 제공한다.
- 서비스 계층에서
TransactionManager.getTransaction()
를 호출해 트랜잭션을 시작한다. - 허나 이 트랜잭션을 시작하려면 커넥션이 필요하다. 트랜잭션 매니저는 생성자 인자로 주입받는 데이터 소스를 통해 커넥션을 만들고 트랜잭션을 시작한다.
setAutoCommit(false)
는 자동 커밋 대신 수동 커밋을 하기 위함이고 수동 커밋을 하게 됨으로써 실제 데이터베이스 트랜잭션을 시작한다.- 만들어진 커넥션을 트랜잭션 동기화 매니저에 보관한다.
- 트랜잭션 동기화 매니저는 커넥션을 쓰레드 로컬에 보관하기 때문에 멀티 쓰레드 환경에서 안전하다.
- 서비스는 비즈니스 로직을 실행하면서 리포지토리 메서드들을 호출한다.
- 리포지토리 메서드의 경우 트랜잭션이 시작된 커넥션을 필요로 한다. 리포지토리는
DataSource.getConnection()
을 통해 트랜잭션 동기화 매니저에 보관된 커넥션을 사용한다. - 획득한 커넥션을 사용해서 SQL을 데이터베이스에 전달해서 데이터 접근을 하게 된다.
- 비즈니스 로직이 끝나고 트랜잭션을 종료한다.
- 트랜잭션을 종료하려면 트랜잭션 동기화 매니저에 보관된 커넥션을 획득한다.
- 획득한 커넥션을 통해 데이터베이스에 트랜잭션 커밋 혹은 롤백을 수행한다.
- 전체 리소스를 정리한다.
- 트랜잭션 동기화 매니저(쓰레드 로컬 정리)
- 수동 커밋을 자동 커밋으로 변경(커넥션 풀을 고려)
- 커넥션 종료(커넥션을 사용한 경우에는 커넥션 풀에 반환까지도)
📚 스프링 AOP를 이용한 트랜잭션
- 프록시를 사용하여 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리한다.
// 프록시 객체
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);
}
... 생략
}
- 클라이언트로부터 요청이 들어오면 프록시가 호출된다.
- 스프링 컨테이너에서 데이터 소스, 트랜잭션 매니저를 스프링 빈으로 관리하며 트랜잭션 매니저를 획득한다.
- 트랜잭션 매니저는 데이터 소스를 생성자 인자로 주입받아 트랜잭션을 시작한다.
- 데이터 소스를 통해 커넥션을 생성한다.
- 만든 커넥션을 수동 커밋으로 변경한다.
- (4) 과정을 통해서 생성된 커넥션을 트랜잭션 동기화 매니저에 보관한다.
- 보관된 커넥션은 쓰레드 로컬에 의해 보관된다.
- 프록시(트랜잭션 처리 담당)가 아닌 비즈니스 로직을 수행하는 서비스를 호출한다.
- 리포지토리에서 트랜잭션 동기화 매니저에 보관된 커넥션을 획득해 데이터 접근 로직을 수행한다.
- 트랜잭션 처리 로직으로 돌아와 성공하면 커밋하고 실패하면 롤백을 한다. 이후 트랜잭션을 종료한다.
❓선언적 트랜잭션 관리 vs 프로그래밍 방식 트랜잭션 관리
@Transactional
어노테이션 하나만 선언해서 사용하는 트랜잭션을 선언적 트랜잭션이라고 한다.- 트랜잭션 매니저, 트랜잭션 템플릿을 사용해서 트랜잭션 관련 코드를 직접 작성해서 사용하는 트랜잭션을 프로그래밍 방식 트랜잭션이라고 한다.
- 스프링 부트를 사용하면 데이터 소스를 자동 등록할 수 있다.
- 데이터 소스 자동 등록 - application.properties / application.yml