[spring mvc] 트랜잭션 적용

Backend kwon·2023년 10월 5일
0

트랜잭션을 적용하는 방법은 크게 두 가지로 나뉩니다.

첫 번째는 작성한 비즈니스 로직에 애너테이션을 추가하는 방식이고, 또 하나는 AOP 방식을 이용해서 비즈니스 로직에서 아예 트랜잭션 적용 코드 자체를 감추는 방식입니다.

여기서는 우선 애너테이션을 사용한 방식만 기록하겠습니다.

 

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

1. 클래스 레벨에 @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);
    }
		
		...
		...
}

위와 같이 하면 해당 클래스에서 memberRepository의 기능을 이용하는 모든 메서드에 트랜잭션이 적용됩니다.

❗JPA 로그 레벨 설정

spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
...
...

logging:         # (1) 로그 레벨 설정
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG

체크 예외(checked exception)는 @Transactional 애너테이션만 추가해서는 rollback이 되지 않습니다.

체크 예외의 경우, 말 그대로 체크를 해야 되는 예외입니다. 따라서 캐치(catch) 한 후에 해당 예외를 복구할지 회피할지 등의 적절한 예외 전략을 고민해 볼 필요가 있을 것입니다.

만일 별도의 예외 전략을 짤 필요가 없다면 @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})와 같이 해당 체크 예외를 직접 지정해 주거나 언체크 예외(unchecked exception)로 감싸서 rollback이 동작하도록 할 수 있습니다.

 

2. 메서드 레벨에 @Transactional 적용

@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(readOnly = true)로 설정하는데 그 이유는 아래와 같습니다.

JPA에서 commit이 호출되면 영속성 컨텍스트가 flush 됩니다.

그런데 @Transactional(readOnly = true)로 설정하면 JPA 내부적으로 영속성 컨텍스트를 flush하지 않습니다.

그리고 읽기 전용 트랜잭션일 경우, 변경 감지를 위한 스냅샷 생성도 진행하지 않습니다.

flush 처리를 하지 않고, 스냅샷도 생성하지 않으므로 불필요한 추가 동작을 줄일 수 있습니다.

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

 

⚫트랜잭션 전파

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

트랜잭션 전파는 propagation 애트리뷰트를 통해서 설정할 수 있으며, 대표적으로 아래와 같은 propagation 유형을 사용할 수 있습니다.

1. Propagation.REQUIRED : @Transactional 애너테이션의 propagation 애트리뷰트에 지정한 Propagation.REQUIRED 는 일반적으로 가장 많이 사용되는 propagation 유형의 디폴트 값입니다.
진행 중인 트랜잭션이 없으면 새로 시작하고, 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여합니다.

2. Propagation.REQUIRES_NEW : 이미 진행 중인 트랜잭션과 무관하게 새로운 트랜잭션이 시작됩니다. 기존에 진행 중이던 트랜잭션은 새로 시작된 트랜잭션이 종료할 때까지 중지됩니다.

3. Propagation.MANDATORY : Propagation.REQUIRED는 진행 중인 트랜잭션이 없으면 새로운 트랜잭션이 시작되는 반면, Propagation.MANDATORY는 진행 중인 트랜잭션이 없으면 예외를 발생시킵니다.

4. Propagation.NOT_SUPPORTED : 트랜잭션을 필요로 하지 않음을 의미합니다. 진행 중인 트랜잭션이 있으면 메서드 실행이 종료될 때까지 진행 중인 트랜잭션은 중지되며, 메서드 실행이 종료되면 트랜잭션을 계속 진행합니다.

5. Propagation.NEVER : 트랜잭션을 필요로 하지 않음을 의미하며, 진행 중인 트랜잭션이 존재할 경우에는 예외를 발생시킵니다.

 

(참고)🤔트랜잭션 격리레벨
트랜잭션은 다른 트랜잭션에 영향을 주지 않고, 독립적으로 실행되어야 하는 격리성이 보장되어야 하는데 Spring은 이러한 격리성을 조정할 수 있는 옵션을 @Transactional 애너테이션의 isolation 애트리뷰트를 통해 제공하고 있습니다.

Isolation.DEFAULT : 데이터베이스에서 제공하는 기본 값입니다.

Isolation.READ_UNCOMMITTED : 다른 트랜잭션에서 커밋하지 않은 데이터를 읽는 것을 허용합니다.

Isolation.READ_COMMITTED : 다른 트랜잭션에 의해 커밋된 데이터를 읽는 것을 허용합니다.

Isolation.REPEATABLE_READ : 트랜잭션 내에서 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 합니다.

Isolation.SERIALIZABLE : 동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션이 수행되지 못하도록 합니다.

트랜잭션의 격리 레벨은 일반적으로 데이터베이스나 데이터소스에 설정된 격리 레벨을 따르는 것이 권장되므로, 이러한 격리 레벨이 있다정도의 가벼운 이해만 하면 됩니다.

profile
백엔드개발자를 향해서

0개의 댓글