[Section 3] 트랜잭션 처리하기

Kim·2022년 11월 7일
0

Boot Camp

목록 보기
44/64
post-thumbnail

트랜잭션은 신뢰할 수 있는 애플리케이션을 구축하기 위해 가장 중요한 부분 중 하나이다.
트랜잭션은 크게 로컬 트랜잭션과 분산 트랜잭션으로 구분할 수 있으며, Spring에서 사용되는 트랜잭션 방식은 선언형 트랜잭션 방식과 프로그래밍 코드 베이스 트랜잭션 방식이 있다.

트랜잭션은 애플리케이션의 핵심 로직이 아닌 부가 기능이기 때문에 AOP의 적용 대상 중 하나라고 볼 수 있는데, 애플리케이션 코드 내에서 프로그래밍 코드 베이스로 트랜잭션을 적용하는 방식은 적절하지 않다.


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

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

Spring에서 트랜잭션을 적용하는 가장 간단한 방법은 @Transactional 애너테이션을 트랜잭션이 필요한 영역에 추가해 주는 것이다.

@Service
@Transactional   // (1)
public class MemberService {
    private final MemberRepository memberRepository;

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

    public Member createMember(Member member) {
        verifyExistsEmail(member.getEmail());

        return memberRepository.save(member);
    }
		
		...
		...
}

@Transactional 애너테이션을 클래스 레벨에 추가하면 기본적으로 해당 클래스에서 MemberRepository의 기능을 이용하는 모든 메서드에 트랜잭션이 적용된다.

...

logging:
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG

애플리케이션을 실행 시키기 전에 트랜잭션이 어떻게 적용되는지 로그로 확인할 수 있도록 JPA의 로그 레벨을 application.yml에 추가했다.
로그 레벨을 DEBUG 레벨로 설정하면 JPA 내부에서 DEBUG 로그 레벨을 지정한 부분의 로그를 확인할 수 있다.

@Service
@Transactional
public class MemberService {
    private final MemberRepository memberRepository;

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

    public Member createMember(Member member) {
        verifyExistsEmail(member.getEmail());
        Member resultMember = memberRepository.save(member);

				
        if (true) {    // (1)
            throw new RuntimeException("Rollback test");
        }
        return resultMember;
    }
		
		...
		...
}

rollback 테스트를 위해 createMember() 메서드를 수정했다.
createMember() 메서드에서 회원 정보를 저장하고 메서드가 종료되기 전에 강제로 RuntimeException이 발생하게 했다.

@Service
@Transactional  // (1)
public class MemberService {
   private final MemberRepository memberRepository;

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

	// (2)
   @Transactional(readOnly = true)
   public Member findMember(long memberId) {
       return findVerifiedMember(memberId);
   }
		
		...
		...
}

메서드 레벨에 @Transactional을 적용하는 코드다.
1과 같이 앞에서 이미 추가한 클래스 레벨 @Transactional 애너테이션 이외에 2와 같이 findMember() 메서드에 @Transactional(readOnly = true)를 추가했다.
이 경우, findMember() 메서드는 읽기 전용 트랜잭션이 적용된다.

조회 메서드에 @Transactional(readOnly = true) 로 설정하는 이유

조회 메서드에는 readonly 속성을 true로 지정해 JPA가 자체적으로 성능 최적화 과정을 거치게 하는 것이 좋다.

클래스 레벨과 메서드 레벨의 트랜잭션 적용 순서

  • 클래스 레벨에만 @Transactional이 적용된 경우
    클래스 레벨의 @Transactional 애너테이션이 메서드에 일괄 적용된다.
  • 클래스 레벨과 메서드 레벨에 함께 적용된 경우
    메서드 레벨의 @Transactional 애너테이션이 적용된다.
    만약 메서드 레벨에 @Transactional 애너테이션이 적용되지 않았을 경우, 클래스 레벨의 @Transactional 애너테이션이 적용된다.

🔑Key Summary

  • 트랜잭션 관련 설정은 Spring Boot이 내부적으로 알아서 해주기 때문에 개발자가 직접적으로 트랜잭션 설정해줄 필요가 없다.

  • Spring에서는 일반적으로 애너테이션 방식(@Transactional)의 트랜잭션과 AOP 방식의 트랜잭션 적용 방식을 사용한다.

  • 체크 예외(checked exeption)는 @Transactional 애너테이션만 추가해서는 rollback이 되지 않는다. @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})와 같이 해당 체크 예외를 직접 지정해주거나, 언체크 예외(unchecked exception)로 감싸야 rollback 기능을 적용할 수 있다.

  • 트랜잭션 전파란 트랜잭션의 경계에서 진행 중인 트랜잭션이 존재할 때 또는 존재하지 않을 때, 어떻게 동작할 것인지 결정하는 방식을 의미한다.

  • @Transactional 애너테이션의 isolation 애트리뷰트를 통해 트랜잭션 격리 레벨을 지정할 수 있다.


참고 자료

📄 Annotation Type Transactional

📄 Class TransactionInterceptor
📄 Interface TransactionManager
📄 Class NameMatchTransactionAttributeSource

0개의 댓글