[Spring] 선언형 방식의 트랜잭션 적용

zini9188·2023년 3월 1일
0

Spring

목록 보기
27/33

선언형 방식의 트랜잭션 적용

선언형 방식으로 트랜잭션을 적용하는 방법은 크게 두 가지가 있다.

  • 비즈니스 로직에 애너테이션을 추가하는 방식

  • AOP 방식을 이용하여 비즈니스 로직에서의 코드를 감추는 방식

애너테이션 방식의 트랜잭션 적용

클래스 레벨이나 메서드 레벨에에 @Transcational 애너테이션을 추가하여 Service 내에 Repository의 기능을 이용하는 모든 메서드나 특정 메서드에 트랜잭션을 적용할 수 있다.

체크 예외에서의 롤백

Exception, SQLException 등 체크 예외는 @Transactional 애너테이션만으로 rollback을 시킬 수 없다.

체크 예외는 예외를 캐치한 후 해당 예외를 복구할지 회피할지 등의 적절한 예외 전략을 고민하여 사용해야 한다. 만약 별도의 예외 전략이 없다면, @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})로 해당 체크 예외를 직접 지정하거나 언체크 예외로 감싸 rollback이 동작할 수 있도록 해야한다.

@Transactional의 속성

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 방식의 트랜잭션 적용

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());  
    }
}
  1. @Configuration 애너테이션을 통한 클래스 정의

  2. TranscationManager 객체 의존성 주입

  3. 트랜잭션 어드바이스용 TranscationInterceptor 빈 등록

    • 대상 클래스 또는 인터페이스에 트랜잭션 경계를 설정하고 적용할 수 있다.

    • (1),(2): 메서드 이름 패턴에 따라 구분해서 트랜잭션 속성 적용 가능

    • (3): 트랜잭션을 적용할 메서드에 트랜잭션 속성 매핑

    • (4): TransactionInterceptor 객체 생성

  4. Advisor 빈 등록

    • (5): TranscationInterceptor를 타겟 클래스에 적용하기 위해 포인트 컷 지정하고 AspectJExpressionPointcut 객체를 생성하여, CoffeeService 클래스를 타겟 클래스로 지정

    • (6): 포인트 컷과 어드바이스를 전달

profile
백엔드를 지망하는 개발자

0개의 댓글