웹 애플리케이션을 만들다보면 트랜잭션을 시작하고 비즈니스 로직을 실행한다. 그리고 제대로 실행된다면 커밋하고 예외가 발생해서 실패하면 롤백하는 동일한 과정을 만들게 되는데 동일하게 반복되는 코드를 매번 작성하는 것은 상당히 비효율적이라고 할 수 있다.
주로 'try , 'catch', 'finally'를 사용하게 되는데 이런 코드에서 달라지는 부분은 비즈니스 로직이 실행되는 코드 뿐이다. 이렇게 반복되는 코드 사용을 줄여서 효율적으로 코딩할 수 있도록 도와주는 것이 트랜잭션 템플릿이다. 즉, 달라지는 서비스코드를 제외한 나머지 반복되는 트랜잭션 코드들은 하나의 템플릿을 사용하는 것이다.
TransactionStatus status = transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
//비즈니스 로직을 실행하는 코드
logic(fromId,toId, money );
transactionManager.commit(status);
} catch(Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
트랜잭션 코드가 반복되는 문제를 해결하기 위해 템플릿 콜백 패턴을 적용해본다.
스프링이 제공하는 'TransactionTemplate' 기능을 사용하면 트랜잭션을 시작하고 커밋 또는 롤백되는 코드를 매번 작성하는 수고로움을 덜 수 있다. 템플릿 콜백 패턴을 적용하려면 템플릿을 제공하는 클래스를 작성해야 한다. 스프링에서 제공하는 'TransactionTemplate' 템플릿 클래스를 사용해보자.
private final TransactionTemplate template;
private final MemberRepositoryV3 memberRepository;
public MemberServiceV3_2(PlatformTransactionManager transactionManager,
MemberRepositoryV3 memberRepository) {
this.template = new TransactionTemplate(transactionManager);
this.memberRepository = memberRepository;
}
TransactionTemplate에 transactionManager를 주입한다.
template.executeWithoutResult((status) -> {
//비즈니스 로직
try {
logic(fromId,toId, money );
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
template의 executeWithoutResult를 사용하고 안에 비즈니스 로직 코드만 하나 넣어준다. 그리고 예외 처리를 위해 try, catch문을 사용했다. 람다에서 체크 예외를 밖으로 던질 수 없기 때문에 언체크 예외로 바꾸어 던지도록 전환한다. 코드를 자세히 살펴보면 executeWithoutResult 안에서 트랜잭션을 시작하면 그 다음으로 비즈니스 로직이 실행된다. 비즈니스 로직이 성공적으로 실행되면 커밋을 하고 예외가 발생해 실패하면 롤백을 하는 방식으로 동작한다.
트랜잭션 템플릿 사용전 코드
TransactionStatus status = transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
//비즈니스 로직 수행
//커넥션을 넘기지 않아도 된다
logic(fromId,toId, money );
//성공하면 커밋
transactionManager.commit(status);
//예외가 발생할 경우 처리
} catch(Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
} //트랜잭션이 커밋되거나 롤백되면 알아서 커넥션이 종료된다
트랜잭션 템플릿 적용 코드
template.executeWithoutResult((status) -> {
//비즈니스 로직
try {
logic(fromId,toId, money );
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
기존 코드와 비교해보면 확실히 코드가 간결해진 것을 확인할 수 있다. 트랜잭션 템플릿을 적용한 코드를 보면 기존에 트랜잭션을 시작하고 커밋이나 롤백하는 코드를 직접 개발자가 작성하지 않아도 된다. 즉, 보다 간편하게 트랜잭션을 시작하고 종료할 수 있다.
해당 코드의 문제점이 없는 것은 아니다. 바로 핵심 로직이라고 할 수 있는 서비스 로직과 트랜잭션을 처리하는 기술 로직이 한 코드로 묶여 있다는 점이다. 이렇게 핵심 기능과 부가기능이 들어간 코드가 하나로 섞여 있으면 향후 유지보수가 어려워지게 된다. 따라서 서비스 로직은 서비스 로직으로 따로 분리되어야 할 필요가 있는데 어떻게 하면 트랜잭션과 서비스 로직을 분리해서 코드를 작성할 수 있을까? 다음에 이런 문제를 해결하는 과정을 공부해 보고자한다.
이 글은 김영한님의 스프링 DB 1편 - 데이터 접근 핵심 원리 강의를 듣고 정리한 내용입니다.