본문 바로가기
부트캠프 개발일기/Spring MVC

52일차: Transaction(트랜잭션)

by shyun00 2023. 4. 26.

Transaction(트랜잭션)

Transaction은 31일차 SQL 학습에서 가볍게 다루고 넘어갔던 내용이다.

여러개의 작업을 하나의 그룹처럼 묶어서 처리하는 작업 단위이다.

작업 중 하나라도 처리에 실패하면 전체가 실패한 것으로 간주하며, 모든 작업이 성공해야 정상적으로 종료되는 단위를 말한다.

트랜잭션은 ACID의 특성을 가진다.

(참고: 2023.03.28 - [부트캠프 개발일기/Database] - 부트캠프 31일차 - SQL, ACID)

 

  • Commit: 모든 작업을 최종적으로 데이터베이스에 반영하는 명령어이다.
          commit 명령을 수행하면 하나의 트랜잭션이 종료되며 변경된 내용이 데이터베이스에 영구적으로 저장된다.
  • Rollback: 작업 중 문제가 발생했을때 트랜잭션에서 수행했던 작업을 취소하는 명령어이다.
          작업 중 하나라도 정상적으로 수행되지 않으면 모든 작업이 트랜잭션 이전의 상태로 돌아간다.

❯ Transaction 분류

트랜잭션은 크게 두가지로 구분할 수 있다.

  • 로컬트랜잭션: 단일 데이터베이스 내에서 실행되는 트랜잭션
  • 분산트랜잭션: 두개 이상의 데이터베이스 서버에서 실행되는 트랜잭션

이번 과정에서는 로컬 트랜잭션에 관한 내용들을 다루고 있다.

(심화 과정으로 분산 트랜잭션 내용이 있어서 시도해보았으나... 아직은 기초 학습이 더 필요할 것 같다😢)

 

Spring에서 사용되는 트랜잭션 방식도 크게 두가지로 나누어 설명할 수 있다.

 

  • 선언형 트랜잭션 방식: ①비즈니스 로직에 애너테이션을 추가하는 방식과
                                      ②AOP를 활용해 비즈니스 로직에서 트랜잭션을 감추는 방법이 있다.
  • 프로그래밍 코드 베이스 트랜잭션 방식: 트랜잭션 매니저를 직접 호출해서 트랜잭션 시작과 종료를 추가하는 방법이다.
                                      트랜잭션은  AOP 적용 대상이므로, 코드내에서 직접 적용하는 방식은 지양한다.

❯ 선언형 트랜잭션 적용

Spring Boot를 사용하고 있기 때문에 트랜잭션을 위한 설정을 별도로 할 필요는 없다.

트랜잭션 작업 중 예외가 발생할 경우 아래와 같은 로직을 따른다.

 

체크예외가 발생할 경우 해당 예외가 처리되지 않고 상위 메서드로 전파되므로 자동으로 Rollback되지 않는다.

따라서 상위 메서드에서 체크예외를 catch해서 예외처리를 해주어야한다.

만약 체크예외가 트랜잭션을 Rollback 시키려면 @Transactional(rollbackFor =  "해당 예외")를 등록해주면 된다.

 

반면 런타임 예외가 발생하면 스프링 프레임워크가 자동으로 트랜잭션을 롤백하게된다.

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

클래스 레벨에 @Transactional 을 적용할 수 있다.

클래스 레벨에 @Transactional 애너테이션을 사용하면 해당 클래스의 모든 public 메서드에 트랜잭션을 적용하게 된다.

@Service
@Transactional // 클래스 단위로 트랜잭션 애너테이션을 추가할 수 있다.
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /* 클래스 내부의 모든 public 메서드에 트랜잭션이 적용된다.
       메서드 내용 중 실패하는 작업이 있으면 rollback해서 처음 상태로 돌아온다.*/
    public Member createMember(Member member) {
        verifyExistsEmail(member.getEmail());
        Member resultMember = memberRepository.save(member);
        return resultMember;
    }
    (...)
}

메서드 레벨에 @Transactional 을 적용할 수 있다.

메서드 레벨에 @Transactional(+ Attribute) 애너테이션을 사용해 트랜잭션의 속성을 설정해줄 수 있다.

/* Propagation.REQUIRED를 적용하면 진행중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고,
   진행중인 트랜잭션이 없으면 새로 시작한다. (propagation 유형의 디폴트 값임) */
@Transactional(propagation = Propagation.REQUIRED)
public Member updateMember(Member member) {
    Member findMember = findVerifiedMember(member.getMemberId());

    (...)

    return memberRepository.save(findMember);
}

/* 읽기 전용 트랜잭션이 된다. commit 절차를 수행하기는 하지만 JPA내부적으로는 영속성 컨텍스트를 flush하지 않는다.
   flush 절차를 수행하지 않고, 스냅샷도 생성하지 않기 때문에 성능 최적화가 가능하다. */
@Transactional(readOnly = true)
public Member findMember(long memberId) {
    return findVerifiedMember(memberId);
}

클래스 레벨에만 @Transactional을 적용하면 해당 클래스 메서드에 트랜잭션이 일괄 적용되고,

클래스 레벨과 메서드 레벨에 @Transactional 이 모두 적용된 경우 메서드 레벨의 @Transaction 이 적용된다.

@Transactional의 애트리뷰트(Attribute) 종류

  • 트랜잭션 전파(Propagation): 트랜잭션 경계에서 진행중인 트랜잭션이 존재하거나 존재하지 않을 때 어떻게 동작할지 결정하는 방법
Propagation.REQUIRED 진행중인 트랜잭션이 없으면 새로 시작하고, 진행중인 트랜잭션이 있으면 해당 트랜잭션에 참여함
Propagation.REQUIRES_NEW 트랜잭션 진행 유무와 관계없이 새로운 트랜잭션을 시작함. 기존 진행중이던 트랜잭션이 있으면 새로 시작된 트랜잭션 종료될때까지 중지됨.
Propagation.MANDATORY 진행중인 트랜잭션이 없으면 예외를 발생시킴
Propagation.NOT_SUPPORTED 트랜잭션이 필요하지 않은 동작. 진행중인 트랜잭션이 있다면 해당 메서드 종료될때까지 중지됨.
Propagation.NEVER 트랜잭션이 필요하지 않은 동작. 진행중인 트랜잭션이 있다면 예외를 발생시킴
  • 트랜잭션 격리 레벨(Isolation Level): 다른 트랜잭션과의 격리성을 조절하는 방법
Isolation.DEFAULT 데이터베이스에서 제공하는 기본값.
Isolation.READ_UNCOMMITTED 다른 트랜잭션에서 커밋하지 않은 데이터를 읽는것을 허용함
Isolation.READ_COMMITED 다른 트랜잭션에 의해 커밋된 데이터를 읽는것을 허용함
Isolation.REPEATABLE_READ 트랜잭션 내에서 한번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 함
Isolation.SERIALIZABLE 동일한 데이터에 대해 동시에 두개 이상의 트랜잭션이 수행되지 못하도록함

② AOP 방식의 트랜잭션 적용

  1. AOP 방식으로 트랜잭션을 적용하기 위해 Configuration 클래스를 정의한다.
        => 설정파일에 TransactionManager를 DI 받는다.
  2. TransactionInterceptor를 스프링 빈으로 등록한다.
        => TransactionInterceptor를 이용해 대상 클래스 혹은 인터페이스에 트랜잭션 경계를 설정하거나 적용할 수 있다.
        => 트랜잭션 애트리뷰트 지정: 트랜잭션은 메서드 이름 패턴에 따라 지정 가능하므로 종류를 구분해 애트리뷰트를 생성한다.
        => 트랜잭션 애트리뷰트와 메서드 매핑: 적용될 메서드와 설정한 애트리뷰트를 value, key 형태로
                                                                     Map<String, TransactionAttribute>에 추가해준다.
        => Map 객체를 NameMatchTransactionAttributeSource에 넣어준다.
        => 만들어진 Source를 활용해서 TransactionInterCeptor 객체를 생성한다.
  3. Advisor를 스프링 빈으로 등록한다.
        => TransactionInterceptor를 타깃 클래스에 적용하기 위한 포인트컷을 지정한다.
        => AspectExpressionPointcut 객체를 생성하여 표현식을 통해 타깃 클래스를 지정한다.
        => DefaultPointcutAdvisor 생성자 파라미터로 포인트컷과 어드바이스를 전달해서 Advisor 객체를 생성한다.