티스토리 뷰

propagation이란?

스프링에서 트랜잭션이 걸린 새로운 함수를 호출할 때 진행 중인 트랜잭션 안에서 처리할지 따로 처리할지 개발자가 옵션을 선택할 수 있게 해주는 속성이다. (번식, 전파)

 

트랜잭션이란?
여러 연산을 하나의 연산으로 묶어주는 역할을 한다.
데이터 정합성을 보장하기 위해 사용한다.
ex) A가 B에게 계좌이체할 때 A 계좌에서 출금이 이루어지고, B 계좌에서 입금이 이루어진다.
이때 출금, 입금 중 하나라도 실패를 하게 되면 데이터 정합성 불일치 문제가 생길 것이고
(A 계좌에서 출금만 이루어짐 or B 계좌에서 입금만 이루어짐),
이는 서비스 운영 시 심각한 문제를 야기할 수 있다. 

 

 

대표적인 두 가지 속성

REQUIRED, REQUIRES_NEW 대표적으로 두가지 속성이 사용된다.

 

REQUIRED

트랜잭션을 여러 함수에서 공유하며 사용할 수 있는 속성이다.

 

예제 코드

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private int money;
    ...
}
@Entity
public class TransactionHistory {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Member member;
   	
    private int amount;
    ...
 }
@Service
public class MemberService {
    private final MemberRepository memberRepository;
    private final TransactionService transactionService;

    @Transactional
    public void buySomething(Integer memberId, int amount) {
        Member member = memberRepository.findById(memberId).orElseThrow();
        member.updateMoney(member.getMoney() - amount);

        transactionService.createTransactionHistory(member, amount);

	// 의도적으로 예외 발생시킴
        throw new RuntimeException("새로운 트랜잭션이 끝난 후 예외발생");
    }
}
@Service
public class TransactionService {
    private final TransactionHistoryRepository transactionHistoryRepository;

    // REQUIRED는 propagation 기본 속성이므로 생략 가능
    @Transactional(propagation = Propagation.REQUIRED)
    public void createTransactionHistory(Member member, int amount) {
        TransactionHistory transactionHistory = new TransactionHistory(member, amount);
        transactionHistoryRepository.save(transactionHistory);
    }
}

 

MemberService의 buySomething에서 트랜잭션 시작 후 propagation이 REQUIRED 속성 트랜잭션이 걸린
TransactionService의 createTransactionHistory를 호출한다.

 

REQUIRED 속성은 트랜잭션을 공유하기 때문에  두 함수에서 사용된 select, insert, update 연산이 한 트랜잭션에서 실행된다.

위 예제는 결국 한 트랜잭션 안에서 연산이 이루어지고, 트랜잭션 마지막에 의도적으로 예외를 발생시켰기 때문에 롤백이 수행될 것이다.

 

기본적으로 @Transactional이 걸린 함수에서 런타임 예외가 발생하면 진행 중인 트랜잭션에서 롤백을 수행한다.
컴파일 예외가 발생하면 커밋이 수행되는데 rollbackFor 속성으로 컴파일 예외도 롤백이 되도록 설정할 수 있다.

 

createTransactionHistory가 끝나고 진행 중인 트랜잭션에서 의도적으로 예외를 발생시켜 createTransactionHistory에서 저장한 TracsactionHistory가 롤백이 되는지 테스트 코드로 확인해 볼 것이다.

 

@DisplayName("Propagation이 REQUIRED인 트랜잭션이 걸린 함수를 호출하고, 해당 트랜잭션 종료 후 진행 중인 트랜잭션에서 예외가 발생하면 롤백이 되는지 확인한다")
@Test
void propagation_REQUIRED_test() {
    // when
    try {
        memberService.buySomething(6, 20_000);
    } catch (RuntimeException ignored) {}

    // then
    List<TransactionHistory> transactionHistories = transactionHistoryRepository.findAll();
    // 롤백이 되면 데이터 개수는 0개가 된다
    assertThat(transactionHistories).hasSize(0);
}

 

테스트는 성공한다(한 트랜잭션 안에서 연산이 수행되므로 수행된 연산은 모두 롤백)

 

 

REQUIRES_NEW

트랜잭션을 공유하지 않고, 독립적인 트랜잭션에서 연산을 수행하는 속성이다.

 

@Service
public class TransactionService {
    private final TransactionHistoryRepository transactionHistoryRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createTransactionHistory(Member member, int amount) {
        TransactionHistory transactionHistory = new TransactionHistory(member, amount);
        transactionHistoryRepository.save(transactionHistory);
    }
}

 

기존 코드에서 속성을 REQUIRES_NEW로 변경했다.

이제 buySomething의 트랜잭션과 createTransactionHistory는 서로 다른 트랜잭션에서 연산이 수행된다.

 

서로 독립적인 트랜잭션이므로 buySomething에서 createTransactionHistory를 호출한 후 의도적으로 예외를 발생시켜도 buySomething에서 수행된 연산만 롤백되고,  createTransactionHistory에서 수행된 연산은 커밋될 것이다.

 

@DisplayName("Propagation이 REQUIRES_NEW인 트랜잭션이 걸린 함수를 호출하고, 해당 트랜잭션 종료 후 진행 중인 트랜잭션에서 예외가 발생하면 롤백이 되는지 확인한다")
@Test
void propagation_REQUIRES_NEW_test() {
    // when
    try {
        memberService.buySomething(6, 20_000);
    } catch (RuntimeException ignored) {}

    // then
    List<TransactionHistory> transactionHistories = transactionHistoryRepository.findAll();
    // 롤백이 되지 않으므로 데이터 개수는 1개가 된다
    assertThat(transactionHistories).hasSize(1);
}

 

테스트는 성공한다 (트랜잭션이 독립적이므로 롤백 전파 ❌)

 

 

propagation에서 REQUIRED, REQUIRES_NEW 말고도 5개의 속성이 더 있지만 자주 사용되지 않기도 하고, 좋은 자료가 있어서 해당 자료로 대체했다.

망나니 개발자 - "[Spring] 스프링의 트랜잭션 전파 속성(Transaction propagation) 완벽하게 이해하기"

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
글 보관함