요즘 트랜잭션이 필요한 비즈니스 로직에는 @Transactional 애노테이션이면 끝난다.
그럼 왜 @Transactional이라는 애노테이션으로 어떻게 진화되어 왔을까라는 의문이 든다.
데이터 접근 기술의 종속성을 걷어내기 위해 여러 가지 방법을 사용할 수 있다.
1. 트랜잭션 매니저를 사용하는 트랜잭션 템플릿 활용
비지니스비즈니스 로직에서 트랜잭션을 얻고 시작하여 try / catch문에 commit과 rollback이 존재하는 경우는 완전하게 비즈니스 로직에 대한 책임만을 가지고 있다고 보긴 힘들다.
public void transactionEX(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이다.
TransactionTemplate는 템플릿 콜백 패턴으로 스프링에서 자주 사용되는 패턴을 활용한 것이다.
이는 변하는 부분 (비지니스 로직)을 인터페이스 화하여 클라이언트 쪽에 해당 내용을 익명 내부 클래스로 정의하여 유연하게 변경하는 부분이다.
public class TransactionTemplate extends DefaultTransactionDefinition
implements TransactionOperations, InitializingBean {
@Nullable
private PlatformTransactionManager transactionManager;
}
선언 부만을 보면 트랜잭션 템플릿 또한 트랜잭션 매니저를 사용하는 것을 알 수 있다. 이는 트랜잭션 매니저를 주입하여 사용하는 것을 내포한다.
public class TransactionTemplateEX {
private final TransactionTemplate txTemplate;
public TransactionTemplateEX (PlatformTransactionManager transactionManager, TransactionTemplate txTemplate){
this.txTemplate = new TransactionTemplate(transactionManager);
}
public void logic{
txTemplate.executeWithoutResult( (status) -> {
bizLogic();
});
}
}
트랜잭션 템플릿의 executeWithoutResult 메서드는 말 그대로 반환이 없는 경우 사용한다. 내부 파라미터는 Consumer이다.
다만 이또한 완벽한 분리라 보기 힘들다. logic안에 결국 트랜잭션의 사용 여부가 드러나있기 때문이다.
우리는 이를 깔끔하 제거하는 방법을 알고 있다.
바로 AOP를 활용할 수 있다는 것이다. 다만 스프링 AOP는 런타임 시점에 프락시를 등록해 사용하기에 메서드 외 조인 포인트는 불가능하다.
깔끔하게 우리는 다음과 같이 사용할 수 있다.
@Transactional
public void logic(){
bizLogic();
}
그런데 무작정 @Transactional이라는 애노테이션을 붙인다고 되는 것이 아니다.
스프링에 기능을 사용하기 위해선 해당 대상들은 스프링 빈으로 등록되어야 함을 잊지말자.
로직이 들어가는 서비스를 빈으로 등록하고 주입 받아야하는 대상들도 빈으로 등록해야 한다.
또한 @Transactional 기능을 사용하기 위해선 트랜잭션 매니저가 빈으로 등록되어야 한다.
@TestConfiguration
static class TestConfig{
@Bean
DataSource dataSource() {
return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
}
@Bean
PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
그럼 스프링의 초기화 시점에 @Transactional을 포함한 클래스는 프락시 객체로 생성되어 스프링 빈으로 등록된다.
이후는 여러 패턴을 넣어 트랜잭션의 시작과 종료와 같은 부가 기능을 추가하고 실제 객체의 메서드를 실행하는 것이다.
이를 통해 깔끔한 비지니스 로직만을 남기는 코드를 작성할 수 있다.
정리해보자.
선언적 트랜잭션 관리를 통해 트랜잭션 관련 코드를 분리하여 순수한 비즈니스 로직을 작성할 수 있다.
단지 @Transactional 애노테이션을 붙이기만 하면 스프링이 트랜잭션에 관련된 것을 처리해준다.
(22-10-14)
추가적으로 선언적 트랜잭션은 public 메서드만 적용된다.
스프링의 트랜잭션 AOP 기능은 public 메서드에만 트랜잭션을 적용하도록 기본 설정되어 있다.
따라서 protected, private, package-visible에는 트랜잭션이 적용되지 않는다.
이는 스프링 측에서 막아둔 것이다.
그 이유는 다음과 같다 .
클래스 레벨에 트랜잭션을 걸었다면 불필요한 메서드에 트랜잭션의 시작점이 될 수 있다.
이는 비즈니스 로직의 시작점이 아닐 수 있게 되는데 이 과정에서 불필요한 트랜잭션이 과도하게 설정될 수 있는 것을 사전에 방지한 것이다.
만약 public이 아닌 곳에 @Transactional이 붙어 있으면 예외가 발생하지 않고 단순 무시가 되어 골치가 아플 수 있다.
'Spring|Spring-boot' 카테고리의 다른 글
Spring Transaction Propagation (0) | 2022.10.16 |
---|---|
Spring Transaction Option (0) | 2022.10.15 |
Spring DB(6) (0) | 2022.09.27 |
Spring DB (4) (1) | 2022.09.19 |
스프링 DB (3) (0) | 2022.09.05 |
댓글