선언형 방식의 트랜잭션 적용
선언형 방식으로 트랜잭션을 적용하는 방법은 크게 두 가지가 있다.
비즈니스 로직에 애너테이션을 추가하는 방식
AOP 방식을 이용하여 비즈니스 로직에서의 코드를 감추는 방식
클래스 레벨이나 메서드 레벨에에 @Transcational
애너테이션을 추가하여 Service 내에 Repository의 기능을 이용하는 모든 메서드나 특정 메서드에 트랜잭션을 적용할 수 있다.
Exception, SQLException 등 체크 예외는 @Transactional
애너테이션만으로 rollback을 시킬 수 없다.
체크 예외는 예외를 캐치한 후 해당 예외를 복구할지 회피할지 등의 적절한 예외 전략을 고민하여 사용해야 한다. 만약 별도의 예외 전략이 없다면, @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})
로 해당 체크 예외를 직접 지정하거나 언체크 예외로 감싸 rollback이 동작할 수 있도록 해야한다.
readOnly
: 데이터를 변경하는 작업이 없는 경우 true로 설정하는 것이 좋다. JPA 내부적으로 영속성 컨텍스트를 flush 하지 않고 변경 감지를 위한 스냅샷 생성을 진행하지 않아 불필요한 추가 동작을 줄일 수 있다.
Propagation
: 트랜잭션 전파를 설정하는 속성이다.
Propagation.REQUIRED
: 일반적으로 사용되며 디폴트 값이다. 진행중인 트랜잭션이 없으면 새로 시작하고, 있다면 해당 트랜잭션에 참여한다.
Propagation.REQUIRES_NEW
: 이미 진행중인 트랜잭션과 무관하게 새로운 트랜잭션이 시작되며, 기존 트랜잭션은 새로 시작된 트랜잭션이 종료될 때까지 중지된다.
Propagation.MANDATORY
: 진행중인 트랜잭션이 없으면 예외를 발생한다.
Propagation.*NOT_SUPPORTED*
: 트랜잭션을 필요로 하지 않음을 의미한다. 진행중인 트랜잭션이 있을 경우 메서드 실행이 종료될 때까지 진행중인 트랜잭션을 중지하고 메서드 실행이 종료되면 트랜잭션을 계속 진행한다.
Propagation.*NEVER*
: 트랜잭션을 필요로 하지 않음을 의미한다. 진행중인 트랜잭션이 존재하는 경우 예외를 발생한다.
Isolation
: 격리성을 보장하기 위해 사용되는 속성이다. 일반적으로 데이터베이스나 데이터소스에 설정된 격리 레벨을 따르는 것이 권장된다.
Isolation.DEFAULT
: 데이터베이스에서 제공하는 기본 값
Isolation.READ_UNCOMMITTED
: 다른 트랜잭션에서 커밋하지 않은 데이터를 읽는 것을 허용한다.
Isolation.READ_COMMITTED
: 다른 트랜잭션에 의해 커밋된 데이터를 읽는 것을 허용한다.
Isolation.REPEATABLE_READ
: 트랜잭션 내에서 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 한다.
Isolation.SERIALIZABLE
: 동일한 데이터에 대해 동시에 두 개 이상의 트랜잭션이 수행되지 못하도록 한다.
AOP를 이용하여 @Transcational
애너테이션을 비즈니스 로직에 적용하지 않고 트랜잭션을 적용할 수 있다.
@Configuration
public class TxConfig {
private final TransactionManager transactionManager;
public TxConfig(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Bean
public TransactionInterceptor txAdvice() {
NameMatchTransactionAttributeSource txAttributeSource =
new NameMatchTransactionAttributeSource();
// (1)
RuleBasedTransactionAttribute txAttribute =
new RuleBasedTransactionAttribute();
txAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// (2)
RuleBasedTransactionAttribute txFindAttribute =
new RuleBasedTransactionAttribute();
txFindAttribute.setPropagationBehavior(
TransactionDefinition.PROPAGATION_REQUIRED);
txFindAttribute.setReadOnly(true);
// (3)
Map<String, TransactionAttribute> txMethods = new HashMap<>();
txMethods.put("find*", txFindAttribute);
txMethods.put("*", txAttribute);
txAttributeSource.setNameMap(txMethods);
// (4)
return new TransactionInterceptor(transactionManager, txAttributeSource);
}
@Bean
public Advisor txAdvisor() {
// (5)
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.codestates.coffee.service." +
"CoffeeService.*(..))");
// (6)
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
@Configuration
애너테이션을 통한 클래스 정의
TranscationManager 객체 의존성 주입
트랜잭션 어드바이스용 TranscationInterceptor
빈 등록
대상 클래스 또는 인터페이스에 트랜잭션 경계를 설정하고 적용할 수 있다.
(1),(2): 메서드 이름 패턴에 따라 구분해서 트랜잭션 속성 적용 가능
(3): 트랜잭션을 적용할 메서드에 트랜잭션 속성 매핑
(4): TransactionInterceptor
객체 생성
Advisor 빈 등록
(5): TranscationInterceptor
를 타겟 클래스에 적용하기 위해 포인트 컷 지정하고 AspectJExpressionPointcut 객체를 생성하여, CoffeeService 클래스를 타겟 클래스로 지정
(6): 포인트 컷과 어드바이스를 전달