1편에 이어, 다시 한 번 코드를 봐보자.
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이라는 것이 transactionManager를 한번 감쌌다.
public class TransactionTemplate {
private PlatformTransactionManager transactionManager;
public <T> T execute(TransactionCallback<T> action) { ... }
void executeWithoutResult(Consumer<TransactionStatus> action_ { ...}
}
해당 템플릿을 사용해서 코드를 정리해보자.
public class MemberService {
private final TransactionTemplate txTemplate;
private final MemberRepository memberRepository;
public MemberService(PlatformTransactionManager transactionManager, MemberRepository 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);
}
});
}
오.. 이제 트랜잭션을 시작하고, 커밋하거나 롤백하는 코드가 없어졌다.
해당 템플릿을 통해 비즈니스 로직이 정상 수행되면 커밋하고, UncheckedException(Runtime Exception)의 경우가 발생하면 롤백하고, 그 외의 경우는 커밋한다. 우선 unchecked 예외에만 롤백한다, 즉 checked 예외는 커밋한다로 암기하고 넘어가자.
우선 아까 코드보다는 괜찮아보인다. 하지만 마찬가지로, 서비스 로직인데 비즈니스 로직뿐만이 아닌 트랜잭션을 처리하는 로직도 들어가있다.
서비스 로직은 가급적 비즈니스 로직만 들어가있어야한다. 이를 해결해보자.
우선 결론부터 이야기하자면, Proxy를 통해 이를 해결할 수 있다.
즉, 트랜잭션을 시작하고 커밋이나 롤백을 다른 곳에서 처리해주면 되는 것이다.
기존 로직은 지금 그림과 같았다. 서비스 로직 안에 트랜잭션 로직과 비즈니스 로직이 둘 다 들어가있어 생긴 문제였다.
프록시를 도입하면 다음과 같다.
이제 트랜잭션을 처리하는 부분이 나뉘어졌다.
프록시 코드를 한번 봐보자.
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);
}
}
}
해당 프록시가 비즈니스 로직을 호출해주는 역할을 하고 있다.
우선 충분히 납득된다. 그렇다면 우리는 항상 프로젝트를 만들 때 트랜잭션이 필요하면 해당 프록시 객체를 만들어줘야할까?
결론부터 말하면 아니다. 스프링이 제공하는 AOP 기능을 통해서 깔끔히 해결할 수 있다.
그저 이제 우리는 @Transactional이라는 어노테이션을 붙여주면 된다.
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
bizLogic(fromId, toId, money);
}
}
사실 공부하기 전에는 그냥 무자비하게 @Transactional만 넣은 기억이 있는데 이를 반성하며..
전체적인 흐름을 보며 마친다.
'스프링 강의 필기' 카테고리의 다른 글
db 2편 - 스프링 트랜잭션 전파 (1) | 2023.11.27 |
---|---|
DB 2편 - 스프링 트랜잭션 이해 (1) | 2023.11.22 |
DB 강의 1편 - 트랜잭션 문제 해결(1) (0) | 2023.11.08 |