Spring으로 테스트코드를 작성하다 보면 한 번쯤은 이런 고민을 하게 된다. (안 했으면 죄송함다)
“테스트에 @Transactional 붙여야 하나? 붙이면 자동 DB 데이터 rollback 되이서 편하긴 한데..”
어떤 글은 “무조건 붙여라”고 하고,
어떤 글은 “절대 쓰지 말라”고 한다.
그래서 더 헷갈린다.
결론부터 말하면 정답은 없다.
대신 용도에 따라 명확히 갈린다.
이 글에서는
@Transactional이 정확히 어떤 역할을 하는지- 테스트에서 사용할 때의 진짜 장점과 진짜 단점
- 실무에서 언제 쓰고, 언제 피해야 하는지
를 정리해보려고 한다.
@Transactional 의미
@Transactional은 Spring이 제공하는 선언적 트랜잭션 관리 어노테이션이다.
해당 메서드(또는 클래스)는 트랜잭션 경계 안에서 실행되며,
- 정상 종료 → commit
- 예외 발생 → rollback
여기서 중요한 포인트는 이거다.
원자성 보장은 Spring의 기능이 아니다.
그건 DB 트랜잭션의 ACID 특성이고,
Spring은 트랜잭션을 시작하고 종료해주는 역할을 할 뿐이다.
테스트에서 @Transactional이 특별한 이유
Spring Test 환경에서는 @Transactional이 조금 다르게 동작한다.
@SpringBootTest
@Transactional
class UserServiceTest {
...
}
이렇게 테스트 클래스나 메서드에 붙이면,
테스트 메서드가 끝나는 순간 자동으로 rollback 된다. (기본값)
즉, commit이 발생하지 않는다.
장점 1. DB 상태를 신경 쓰지 않아도 된다
- 매 테스트마다 DB를 초기화하지 않아도 되고
- 테스트 간 데이터가 섞일 일도 없고
- AfterEach에서 delete 치는 코드도 필요 없다
테스트 격리를 아주 싸게 얻을 수 있다!
장점 2. 테스트 코드가 깔끔해진다
@Transactional을 테스트에 붙이면
테스트가 끝날 때마다 자동으로 rollback 되기 때문에
데이터 정리 코드를 전부 제거할 수 있다.
원래라면 이런 코드가 따라온다.
- 테스트 끝나고 deleteAll()
- 테이블 truncate
- 테스트 전용 데이터 정리 로직
그런데 @Transactional을 쓰면 이게 전부 필요 없다.
@SpringBootTest
@Transactional
class UserServiceTest {
@Test
void 회원_저장_테스트() {
userRepository.save(user);
}
}
정리 코드도 없고, 상태 복구 로직도 없다.
단점 1. 실제 환경을 반영하지 못한다
테스트에 @Transactional을 붙이면,
서비스 코드의 트랜잭션 경계가 사라진다.
예를 들어,
@Transactional
public void serviceA() {
repository.save(...);
}
그리고 테스트에서
@Transactional
@Test
void test() {
serviceA();
}
이렇게 되면,
- 실제 서비스: serviceA에서 트랜잭션 시작
- 테스트 환경: 이미 테스트 트랜잭션이 열려 있어서 serviceA는 참여만 함
즉,
트랜잭션 전파, 분리, 중첩 같은 동작을 검증할 수 없게 된다.
트랜잭션 구조 자체가 왜곡된다.
단점 2. 커밋 시점에 터지는 버그를 못 잡는다
운영에서 자주 터지는 버그들을 보면 이런 것들이 있다.
- unique constraint 위반
- flush 시점 오류
- cascade 설정 오류
이런 것들은 대부분 commit 시점에 터진다.
그런데 테스트에서 rollback만 하고 commit을 안 하면?
운영에서는 터질 버그가, 테스트에서는 안 터진다.
그래서 언제 쓰고, 언제 쓰지 말아야 할까?
한 줄 정리
@Transactional은 테스트를 편하게 만들어주지만, 실제 동작과 다르게 동작할 수 있다.
결정은 동료들과 얘기를 해서 결정!
'개발 > Spring Boot' 카테고리의 다른 글
| MDC를 활용한 추적 가능한 로그 남기기 (1) | 2024.04.18 |
|---|---|
| JPA 복합키 @IdClass vs @EmbeddedId (0) | 2023.07.24 |