트랜잭션은 신뢰할 수 있는 애플리케이션을 구축하기 위해 가장 중요한 부분 중 하나이다.
트랜잭션은 크게 로컬 트랜잭션과 분산 트랜잭션으로 구분할 수 있으며, 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
애너테이션이 적용된다.
트랜잭션 관련 설정은 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