트랜잭션

박윤택·2022년 7월 29일
1

Spring

목록 보기
11/18

🤔 트랜잭션이란?

데이터베이스에서 논리적 상태 변화, 즉 Insert, Update, Delete로 데이터베이스의 데이터가 변화가 있는 것을 트랜잭션(transaction)이라고 한다.


특징

ACID 원칙
1. 원자성(Atomicity)
둘 다 성공하던가 둘 다 실패하던가 중에서 하나로만 처리되는 것이 보장되어야 한다.
2. 일관성(Consistency)
트랜잭션이 에러없이 성공적으로 종료될 경우, 일관성있게 저장되거나 변경되는 것을 의미한다.
3. 격리성(Isolation)
여러개의 트랜잭션이 실행될 경우 각각 독립적으로 실행되어야 한다.
4. 지속성(Durability)
트랜잭션이 완료되면 그 결과는 지속적이어야 한다.


커밋(Commit)과 롤백(Rollback)

  • 커밋(Commit)
    변경된 내용이 데이터베이스에 영구적으로 저장된다.
    commit 명령을 수행하면 하나의 트랜잭션 과정은 종료된다.
  • 롤백(Rollback)
    작업 중 문제가 발생하면 트랜잭션 내에서 수행된 작업들을 취소한다.
    트랜잭션 시작 이전의 상태로 돌아간다.

🖥️ Spring에서의 Transacion 처리

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

비즈니스 로직을 수행하는 서비스 단에 @Transactional 어노테이션을 붙여 트랜잭션을 적용할 수 있다. 서비스 클래스나 서비스 클래스의 메서드 레벨에서 어노테이션을 붙일 수 있으며, 클래스 레벨에서 어노테이션을 붙일 경우 메서드에 일괄 적용이 된다. 클래스 레벨, 메서드 레벨 둘다 붙일 경우 메서드 레벨의 @Transactional 어노테이션이 적용된다.

조회 메서드에는 @Transactional(readOnly = true)로 설정하여 자체적으로 성능 최적화 과정을 거치도록 한다. readOnly 옵션을 true로 설정하면 JPA 내부적으로 영속성 컨텍스트를 flush 하지 않고 읽기 전용 트랜잭션일 경우 변경 감지를 위한 스탭샷 생성도 진행하지 않는다. flush 처리도, 스냅샷도 생성하지 않으므로 불필요한 추가 동작을 줄일 수 있다. 그리고 체크 예외는 @Transactional 어노테이션만 추가해서는 rollback이 되지 않아 @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})와 같이 해당 체크 예외를 직접 지정해주거나 언체크 예외(unchecked exception)로 감싸야 rollback 기능을 적용할 수 있다


여러 작업이 하나의 트랜잭션으로 묶이는 경우


AController에서 AService를 호출하는데 있어서 AService 내에서는 BService 메서드를 호출하고 있다. 이때 여러 작업이 하나의 트랜잭션으로 묶이게 된다.

이때 BService로직에서 예외가 발생하든, AService에서 예외가 발생하든 두 메서드가 하나의 트랜잭션 경계 내에 있으므로 모두 rollback이 된다.


트랜잭션 전파

트랜잭션 경계에서 진행 중인 트랜잭션이 존재할 때 또는 존재하지 않을 때, 어떻게 동작할 것인지 결정하는 것을 말한다.
트랜잭션 전파는 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를 적용한 트랜잭션

@Configuration
public class TxConfig {
    private final TransactionManager transactionManager;
	
    // TransactionManager DI
    public TxConfig(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

	// 트랜잭션 어드바이스용 TransactionInterceptor 빈 등록
    @Bean
    public TransactionInterceptor txAdvice() {
        NameMatchTransactionAttributeSource txAttributeSource =
                                    new NameMatchTransactionAttributeSource();

		// 공통 트랜잭션		
        RuleBasedTransactionAttribute txAttribute =
                                        new RuleBasedTransactionAttribute();
        txAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		// 읽기 전용 트랜잭션		
        RuleBasedTransactionAttribute txFindAttribute =
                                        new RuleBasedTransactionAttribute();
        txFindAttribute.setPropagationBehavior(
                                        TransactionDefinition.PROPAGATION_REQUIRED);
        txFindAttribute.setReadOnly(true);

				
        Map<String, TransactionAttribute> txMethods = new HashMap<>();
        txMethods.put("find*", txFindAttribute);
        txMethods.put("*", txAttribute);

				
        txAttributeSource.setNameMap(txMethods);

				
        return new TransactionInterceptor(transactionManager, txAttributeSource);
    }

    @Bean
    public Advisor txAdvisor() {
				
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        
        // 포인트 컷 지정
        pointcut.setExpression("execution(* 패키지명.서비스클래스.*(..))");

        return new DefaultPointcutAdvisor(pointcut, txAdvice());  
    }
}

0개의 댓글