티스토리 뷰
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) 완벽하게 이해하기"
'Spring' 카테고리의 다른 글
JPA N + 1 원인 및 해결 방법 (1) | 2024.08.02 |
---|---|
@Builder 조심하세요! (0) | 2024.07.29 |
Spring Boot Gradle 멀티 모듈 사용하기 (0) | 2024.07.29 |
Kotlin + Spring 프로젝트에 ktlint 도입하기 (1) | 2024.06.11 |
Webhook을 사용해 slack 메세지 보내기 (2) | 2024.06.04 |
- Total
- Today
- Yesterday
- 인텔리제이
- 성능 개선
- webhook
- lombok
- lint
- 계층형 아키텍처
- embedded redis
- JPA
- 헥사고날 아키텍처
- 자동화
- multi module
- IDE
- detekt
- Spring Boot
- 테스트 코드
- Slack
- Gradle
- propagation
- N + 1
- transaction
- ktlint
- H2
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |