스프링/5. 스프링 DB-1

26. 트랜잭션 템플릿

sdafdq 2023. 10. 1. 19:40

보다보면, 같은 패턴이 반복되는 것이 있다.

 

try.. catch..

커넥션 얻고, 상태 얻고 등

 

public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

        try{
            transferLogic(fromId, toId, money);
            transactionManager.commit(status);
        }catch(Exception e){
            transactionManager.rollback(status);
            throw new IllegalStateException();
        }finally {

        }
    }

예를 들어 서비스에서

getTransaction으로 트랜잭션 시작,

 

try이로 트랜잭션 로직 시도

성공하면 commit

실패하면 rollback

 

우리는 순수한 저 transferLogic만 남기고 싶다.

private void transferLogic(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);
}

 

 

 

트랜잭션템플릿을 활용해 봤다.

@Slf4j
public class MemberServiceV3_2 {
    private final TransactionTemplate txTemblate;
    private final MemberRepositoryV3 memberRepository;

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

    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        txTemblate.executeWithoutResult((status)->{
            try{
                transferLogic(fromId, toId, money);
            } catch (SQLException e) {
                throw new IllegalStateException(e);
            }
        });
    }

    private void transferLogic(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("이체 중 예외 발생");
        }
    }
}

트랜잭션 템플릿을 필드로 둔다.

그런데, 이걸 직접적으로 인자로 주입받지는 않고 (어차피 저거 쓰려면 트랜잭션 매니저를 주입받아야 한다.)

생성자에서 직접 생성하면서 넣어준다.

이렇게 하면 장점이 트랜잭션템플릿은 인터페이스가 아니고 클래스이기 때문에, 유연성이 올라간다.

트랜잭션 매니저는 인터페이스이기 때문에 트랜잭션 매니저를 주입 받아서 쓰도록 하는 게 좋다.

 

사용 방법은

TransactionTemplate.excuteWithdoutResult((status)->{
	로직
})

이다.

TransactionTemplate.excuteWithoutResult가 있고 그냥 excute가 있는데, excuteWithoutResult는 말 그대로 결과가 없을 때 이다. 딱히 받을 값이 없을 때.

 

그리고, 해서 로직이 성공하면 commit해주고, 실패 시 자동으로 rollback 해 준다.

 

excuteWIthoutResult의 내부내용은 아마

setAutoCommit(false), TransactionStatus 얻어오고,

 

위에 try catch 쓴 거는 로직이 SQLException을 던져서 쓴거다.

 

아마 내부적으로도

try{

    콜백함수(로직)

    commit()

}catch (e){

    rollback()

}

 

이런 식으로 감싸져 있을 것이다.

 

그리고, 위에 트랜잭션 부분이랑 순수 비지니스 로직 부분 나눠둔다고 나눴는데,

이제 그러지 말고 그냥 콜백으로 넘기는 게 훨씬 깔끔 할 듯.

 

 

 

물론. 지금 코드 꽤 괜찮다.

그런데 정말 순수 비즈니스 로직과 트랜잭션 부분을 나누고 싶다.

지금은 순수 비즈니스 로직 안에 transactionTemplate.excuteWithoutResult 등, 많지는 않지만 쓰기는 한다.

정말 완전히 분리를 하고 싶다.

 

유지보수를 위해.

지금은 두가지 관심사를 한 클래스에서 처리 중임.

 

'스프링 > 5. 스프링 DB-1' 카테고리의 다른 글

28. 트랜잭션 AOP 적용.  (0) 2023.10.01
27. 트랜잭션 AOP  (0) 2023.10.01
25. 트랜잭션 매니저  (0) 2023.10.01
24. 트랜잭션 동기화  (0) 2023.09.30
23. 트랜잭션 추상화  (0) 2023.09.30