트랜잭션(Transaction)

jungseo·2023년 6월 26일
0

Spring

목록 보기
11/23
post-thumbnail

트랜잭션이란

  • 여러개의 작업을 하나의 그룹으로 묶은 처리 단위
  • 그룹 내의 하나의 작업이라도 실패하면 모든 작업이 실패
  • All or Nothing 처리 방식으로 데이터 무결성 보장
  • 데이터베이스에만 한정해서 사용하지 않음

1. ACID 원칙

  • 원자성(Atomicity)

    • 작업을 더 이상 쪼갤 수 없음을 의미
    • 트랜잭션을 하나의 작업으로 인식해 성공, 실패 하나로만 처리 되는 것이 보장
  • 일관성(Consistency)

    • 트랜잭션이 에러없이 성공적으로 종료될 경우 비즈니스 로직에서 의도대로 일관성 있게 저장 혹은 변경 되는 것
  • 격리성(Isolation)

    • 여러개의 트랜잭션이 실행 될 경우 각각 독립적으로 실행되어야 함
    • 컴퓨터에서 여러 프로그램을 동시에 사용중 일때 CPU는 여러 프로세스를 빠른 속도로 번갈아 가며 실행하는 것과 유사
    • 데이터베이스 역시 여러개의 트랜잭션을 번갈아가며 처리 할 수 있는데 이런 경우 각 트랜잭션이 서로 영향을 주지 않고 독립적으로 실행되어야 함
  • 지속성(Durability)

    • 트랜잭션이 완료되면 그 결과는 지속되어야 함

2. commit / rollback

1) commit

  • 모든 작업을 최종적으로 데이터베이스에 반영
  • 명령 수행 후 트랜잭션 과정 종료
BEGIN TRANSACTION;
insert into  MEMBER VALUES 
       (1, now(), now(), 'hgd1@gmail.com', 'MEMBER_ACTIVE', '홍길동1', '010-1111-1111');
COMMIT;

BEGIN TRANSACTION;
insert into  MEMBER VALUES 
       (2, now(), now(), 'hgd2@gmail.com', 'MEMBER_ACTIVE', '홍길동2', '010-2222-2222');
  • H2 웹 콘솔에 입력 시 commit을 하지 않은 아래 정보는 저장되지 않음

    	Auto commit 기능을 끄고 확인 / 새로 고침을 해줘야 정상적으로 결과가 보인다.
  • JPA API를 사용한 commit 과정에서 JPA 기술을 사용한 데이터베이스와의 인터랙션은 내부적으로는 JDBC API를 통해서 이루어짐

2) rollback

  • 작업 중 문제가 발생했을 때 트랜잭션 내에서 수행된 작업들을 취소
  • 트랜잭션 시작 이전의 상태로 돌아감
BEGIN TRANSACTION;
insert into  MEMBER VALUES 
			(1, now(), now(), 'hgd1@gmail.com', 'MEMBER_ACTIVE', '홍길동1', '010-1111-1111');
ROLLBACK;
COMMIT;
  • H2 웹 콘솔에 입력 시 데이터가 저장되지 않음

선언형 트랜잭션 적용

  • 적용 전 설정파일에 아래 내용을 추가해 DEBUG 레벨의 로그 확인 가능
logging:         # 로그 레벨 설정
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG

1. @Transaction 애너테이션 사용

1) 클래스 레벨에 적용

import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class MemberService {

		...
}
  • 해당 클래스의 모든 메서드에 트랜잭션 적용

  • 커밋 출력 로그

// 트랜잭션 생성
: Creating new transaction with name [com.codestates.member.service.MemberService.createMember]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

// 커밋
: Initiating transaction commit
: Committing JPA transaction on EntityManager [SessionImpl(501161200<open>)]

// 트랜잭션 종료
: Not closing pre-bound JPA EntityManager after transaction

// EntityManager 종료
: Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
  • 롤백 출력 로그
: Initiating transaction rollback
: Rolling back JPA transaction on EntityManager [SessionImpl(1632113004<open>)]
: Not closing pre-bound JPA EntityManager after transaction
: Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

2) 메서드 레벨에 적용

import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class MemberService {

		...
        
    @Transactional(readOnly = true) // (1)
    public Member findMember(long memberId) {
        return findVerifiedMember(memberId);
    }		
		...
}
  • (1) @Transactional(readOnly = true)를 추가
    • 읽기 전용 트랜잭션 적용
    • JPA에서 commit이 호출되면 영속성 컨텍스트가 flush
    • readOnly 설정시 flush 하지 않고 스냅샷도 생성하지 않음
      • 불필요한 추가 동작을 줄일 수 있음
      • 조회 메서드에 readonly 속성을 true로 지정해 JPA 자체적으로 성능 최적화 가능
  • 출력 로그
PROPAGATION_REQUIRED,ISOLATION_DEFAULT, readOnly 

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

  • 클래스 레벨에만 @Transaction이 붙은 경우
    • 메서드 일괄 적용
  • 클래스 레벨과 메서드 레벨에 함께 붙은 경우
    • 메서드 레벨의 @Transactional 적용
    • 메서드 레벨의 @Transactionnal 적용 되지 않았을 경우 클래스 레벨의 @Transactional 적용

4) checked exception

  • Exception, SQLException, DataFormatException 같은 체크 예외(checked exception)는 @Transactional 애너테이션만 추가해서는 rollback 되지 않음
  • catch 후 예외를 복구할지 회피할지 고민
  • 별도의 예외 전략을 짤 필요가 없다면 @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})
    같이 해당 체크 예외를 직접 지정해 주거나 언체크 예외(unchecked exception)로 감싸서 rollback이 동작하도록 가능

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

  • OrderService에서 MemberService의 메서드를 호출시 예외가 발생하도록 수정
@Transactional
@Service
public class OrderService {

		...

    public Order createOrder(Order order) {
        verifyOrder(order);
        Order savedOrder = saveOrder(order);

        updateStamp(savedOrder); // MemberService 메서드 호출

        throw new RuntimeException("rollback test");
//        return savedOrder;
    }
    		...
}
  • OrderService에서 호출되는 MemberService 메서드
@Transactional
@Service
public class MemberService {

		...

//      (1)
    @Transactional(propagation = Propagation.REQUIRED)
    public Member updateMember(Member member) {
        Member findMember = findVerifiedMember(member.getMemberId());

        Optional.ofNullable(member.getName())
                .ifPresent(name -> findMember.setName(name));
        Optional.ofNullable(member.getPhone())
                .ifPresent(phone -> findMember.setPhone(phone));
        Optional.ofNullable(member.getMemberStatus())
                .ifPresent(memberStatus -> findMember.setMemberStatus(memberStatus));

        return memberRepository.save(findMember);
    }
  • @Transactional(propagation = Propagation.REQUIRED)
    • 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 사용, 존재하지 않으면 새 트랜잭션을 생성
    • OrderService에서 트랜잭션이 이미 생성되어 메서드를 호출함

(1) 트랜잭션 전파(Transaction Propagation)

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

  • Propagation.REQUIRED

    • 진행 중인 트랜잭션이 없으면 새로 시작, 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여
  • Propagation.REQUIRES_NEW

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

    • 진행 중인 트랜잭션이 없으면 예외를 발생
  • Propagation.NOT_SUPPORTED

    • 진행 중인 트랜잭션이 있으면 메서드 실행이 종료될 때까지 진행 중인 트랜잭션은 중지, 메서드 실행이 종료되면 트랜잭션을 계속 진행
  • Propagation.NEVER

    • 진행 중인 트랜잭션이 존재할 경우에는 예외를 발생

(2) 트랜잭션 격리 레벨(Isolation Level)

다른 트랜잭션에 영향을 주지 않고 독립적으로 실행되어야 하는 격리성을 @Transactional 애너테이션의 isolation 애트리뷰트를 통해 조정 가능
트랜잭션의 격리 레벨은 일반적으로 데이터베이스나 데이터소스에 설정된 격리 레벨을 따르는 것이 권장

  • Isolation.DEFAULT

    • 데이터베이스에서 제공하는 기본 값
  • Isolation.READ_UNCOMMITTED

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

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

    • 트랜잭션 내에서 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 함
  • Isolation.SERIALIZABLE

    • 동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션이 수행되지 못하도록 함

0개의 댓글