트랜잭션 추상화를 이용해서 JDBC에서 JPA로 바꾸어도 서비스 로직을 안 바꿔도 되게끔 하였다!!

근데 트랜잭션을 manager.getTransaction()으로 실행하고 try 구문에서 비즈니스 로직 실행하고 성공시 커밋, 실패시 catch 구문에서 롤백해야한다는 점 때문에 아래와 같은 코드들이 반복된다. 모든 서비스 로직에서 반복된다!

    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        //트랜잭션 시작
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            //비즈니스 로직
            bizLogic(fromId, toId, money);
            transactionManager.commit(status); //성공시 커밋
        } catch (Exception e) {
            transactionManager.rollback(status); //실패시 롤백
            throw new IllegalStateException(e);
        }

    }

템플릿 콜백 패턴으로 해결 가능하다.

지금은 스프링이 템플릿 콜백 패턴을 TransactionTemplate 이라는 기능을 제공해주는데, 위와 같은 패턴 반복을 간단하게 처리해준다 정도로 이해하고 넘어가자.

public class TransactionTemplate {

	private PlatformTransactionManager transactionManager;
	
	public <T> T execute(TransactionCallback<T> action){..}
	
	void executeWithoutResult(Consumer<TransactionStatus> action){..}
}

execute() : 응답 값이 있을 때 사용한다. executeWithoutResult() : 응답 값이 없을 때 사용한다.

아래 코드와 같이 TransactionTemplete에 도움을 받아 성공시 커밋, 실패시 롤백하는 코드를 단순화 하였다.

TransactionTemplate을 사용하려면 transactionManager가 필요하기 때문에 @RequiredArgsConstructor로 자동주입해주는 거 지우고 생성자를 통해서 직접 주입해주었다!!


/**
 * 트랜잭션 - 트랜잭션 템플릿
 */
@Slf4j
public class MemberServiceV3_2 {

    private final TransactionTemplate txTemplate;
    private final MemberRepositoryV3 memberRepository;

    public MemberServiceV3_2(PlatformTransactionManager transactionManager, MemberRepositoryV3 memberRepository) {
        this.txTemplate = new TransactionTemplate(transactionManager);
        this.memberRepository = memberRepository;
    }

    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        txTemplate.executeWithoutResult((status) -> {
            //비즈니스 로직
            try {
                bizLogic(fromId, toId, money);
            } catch (SQLException e) {
                throw new IllegalStateException(e);
            }
        });
    }

    private void bizLogic(String fromId, String toId, int money) throws SQLException {
        Member fromMember = memberRepository.findById(fromId);
        Member toMember = memberRepository.findById(toId);

        memberRepository.update(fromId, fromMember.getMoney() - money);
        validation(toMember);
        memberRepository.update(toId, toMember.getMoney() + money);
    }

    private void validation(Member toMember) {
        if (toMember.getMemberId().equals("ex")) {
            throw new IllegalStateException("이체중 예외 발생");
        }
    }

}

트랜잭션 템플릿 덕분에 트랜잭션을 시작하고, 커밋하거나 롤백하는 코드가 모두 제거되었다.

트랜잭션 템플릿의 기본 동작은 다음과 같다. (자세한 동작 원리는 스프링 고급편에서…)

코드에서 예외를 처리하기 위해 try~catch 가 들어갔는데, bizLogic() 메서드를 호출하면 SQLException 체크 예외를 넘겨준다. 해당 람다에서 체크 예외를 밖으로 던질 수 없기 때문에 언체크 예외로 바꾸어 던지도록 예외를 전환했다.