JPA 트랜잭션을 제대로 이해하지 않으면 발생하는 장애 사례
Spring Boot에서 @Transactional은 가장 많이 사용하는 기능 중 하나입니다.
예
@Transactional
public void createOrder() {
orderRepository.save(order);
}
public void createOrder() {
orderRepository.save(order);
}
하지만 트랜잭션을 제대로 이해하지 않고 사용하면 성능 문제, 데이터 오류, Deadlock까지 발생할 수 있습니다.
이번 글에서는 실무에서 자주 발생하는 트랜잭션 문제 7가지를 정리합니다.
1. Self Invocation 문제
Spring 트랜잭션은 AOP Proxy 기반으로 동작합니다.
예
@Service
public class OrderService {
public void create() {
save(); // 트랜잭션 적용 안됨
}
@Transactional
public void save() {
orderRepository.save(order);
}
}
public class OrderService {
public void create() {
save(); // 트랜잭션 적용 안됨
}
@Transactional
public void save() {
orderRepository.save(order);
}
}
이 경우
create() → save()
내부 호출이기 때문에 Proxy를 거치지 않습니다.
결과
@Transactional 적용 안됨
해결 방법
서비스를 분리합니다.
@Service
public class OrderService {
private final OrderTxService orderTxService;
public void create() {
orderTxService.save();
}
}
public class OrderService {
private final OrderTxService orderTxService;
public void create() {
orderTxService.save();
}
}
2. 트랜잭션 안에서 외부 API 호출
이것도 실무에서 많이 발생합니다.
@Transactional
public void order() {
paymentApi.call(); // 문제
orderRepository.save(order);
}
public void order() {
paymentApi.call(); // 문제
orderRepository.save(order);
}
문제
API 호출 동안 DB lock 유지
결과
Deadlock
Connection Pool 부족
성능 저하
Connection Pool 부족
성능 저하
해결 방법
외부 API는 트랜잭션 밖에서 실행
3. readOnly 옵션 미사용
조회 쿼리에도 트랜잭션을 사용하지만 readOnly 설정을 안 하는 경우가 많습니다.
@Transactional
public List<Order> findOrders() {
return orderRepository.findAll();
}
public List<Order> findOrders() {
return orderRepository.findAll();
}
이 경우 Hibernate는
Dirty Checking
Flush 관리
Flush 관리
등을 수행합니다.
해결 방법
@Transactional(readOnly = true)
이렇게 설정하면
Flush 비활성화
성능 개선
성능 개선
됩니다.
4. 너무 긴 트랜잭션
예
@Transactional
public void processOrder() {
orderRepository.save(order);
Thread.sleep(3000);
inventoryService.update();
}
public void processOrder() {
orderRepository.save(order);
Thread.sleep(3000);
inventoryService.update();
}
이 경우
3초 동안 row lock 유지
됩니다.
결과
Deadlock
DB lock 증가
DB lock 증가
5. 대량 데이터 처리
예
@Transactional
public void batch() {
List<Order> orders = orderRepository.findAll();
for (Order o : orders) {
o.setStatus("COMPLETE");
}
}
public void batch() {
List<Order> orders = orderRepository.findAll();
for (Order o : orders) {
o.setStatus("COMPLETE");
}
}
문제
Persistence Context 메모리 증가
결과
OutOfMemory
성능 저하
성능 저하
해결 방법
배치에서는
flush
clear
clear
를 사용합니다.
6. 트랜잭션 전파 이해 부족
Spring 트랜잭션에는 여러 propagation 옵션이 있습니다.
예
@Transactional(propagation = Propagation.REQUIRES_NEW)
이 옵션은
새 트랜잭션 생성
을 의미합니다.
잘못 사용하면
트랜잭션 폭증
성능 문제
성능 문제
가 발생할 수 있습니다.
7. Exception 처리 문제
Spring은 기본적으로
RuntimeException
만 rollback 합니다.
예
@Transactional
public void order() throws Exception {
orderRepository.save(order);
throw new Exception();
}
public void order() throws Exception {
orderRepository.save(order);
throw new Exception();
}
이 경우
rollback 안됨
입니다.
해결 방법
@Transactional(rollbackFor = Exception.class)
정리
Spring Boot에서 트랜잭션을 사용할 때 반드시 다음을 이해해야 합니다.
AOP Proxy 구조
트랜잭션 범위
Lock 유지 시간
Propagation
Rollback 규칙
트랜잭션 범위
Lock 유지 시간
Propagation
Rollback 규칙
✔ 핵심 한 줄
@Transactional은 기능이 아니라 동시성 제어 도구다.
LIST
'DB' 카테고리의 다른 글
| [Database] 스키마 관리의 두 철학: Prisma Migrate vs Flyway 전격 비교 (0) | 2026.03.17 |
|---|---|
| JPA를 사용하면 성능이 느려진다고 하는 이유(N+1문제) (2) | 2026.03.11 |
| Spring Boot에서 DB 커넥션 풀 크기를 계산하는 방법 (0) | 2026.03.11 |
| Spring Boot HikariCP 설정을 잘못하면 발생하는 5가지 장애 (0) | 2026.03.11 |
| Spring Boot HikariCP 커넥션 풀 설정 실무 가이드 (0) | 2026.03.11 |
