신비한 개발사전
@Transactional 유무에 의한 로직 동작 차이 본문
트랜잭션은 작은 작업들을 한 작업처럼 몰아서 처리하는 하나의 실행 단위다. 함께 처리해야 하는 작업들 중 하나라도 문제가 생겼을 때, 일부 성공한 작업들도 처음부터 수행되지 않은 것처럼 롤백(rollback)을 하기 위해서는 트랜잭션으로 실행되어야 한다.
Spring 애플리케이션에서는 @Transactional 애노테이션을 통해 메서드가 트랜잭션 단위로 실행되도록 제어할 수 있다.
트랜잭션이 필요한 대표적인 예시로는 DB의 데이터 변경 로직과 다른 비즈니스 로직이 섞여있는 경우가 있다. 아래 예시에서는 @Transactional이 없으면 문제가 발생할 수 있다.
@Service
public class ReservationService {
private final ReservationRepository reservationRepository;
public ReservationService(ReservationRepository reservationRepository) {
this.reservationRepository = reservationRepository;
}
private ReservationResponse createReservation(Reservation reservation) {
Reservation newReservation = reservationRepository.save(reservation);
if (true) throw new IllegalStateException("위 save 작업이 DB에 반영되지 않아야 함");
return ReservationResponse.from(newReservation, newReservation.getTime(), newReservation.getTheme());
}
}
@Transactional 애노테이션이 붙어있지 않은 상태에서 createReservation 메서드 내에 repository.save()를 호출한 후 예외를 터트려봤다.
중간에 오류가 발생했기 때문에 결과적으로는 createReservation 내에서 일어난 작업들은 함께 실패로 처리돼야 마땅하다고 생각할 수 있다.
하지만 IllegalStateException이 발생해도 repository.save()로 저장한 데이터는 DB에 그대로 남아있다! 외부 인프라를 조작하는 메서드는 이미 독립적으로 일어난 일이어서 이후 line에서 발생하는 문제에 영향을 받지 않는다.
트랜잭션 단위로 처리하면 예외가 발생했을 때 repository.save() 호출 결과가 취소(롤백)될 수 있다.
@Service
public class ReservationService {
// ...중략...
@Transactional
private ReservationResponse createReservation(Reservation reservation) {
Reservation newReservation = reservationRepository.save(reservation);
// 이제 예외가 발생하면 DB에 reservation 데이터가 저장되지 않는다
if (true) throw new IllegalStateException("위 save 작업이 DB에 반영되지 않아야 함");
return ReservationResponse.from(newReservation, newReservation.getTime(), newReservation.getTheme());
}
}
테스트에서 @Transactional을 사용할 땐 유의할 점이 있다.
테스트 클래스나 각 @Test 메서드에도 @Transactional을 적용해 트랜잭션 단위로 테스트 메서드를 수행할 순 있지만, 해당 테스트 내에서 RestAssured를 사용하면 RestAssured가 또다른 트랜잭션을 생성해버려서 각각 다른 트랜잭션 환경이 만들어진다.
이 상태에서는 테스트 메서드 내에서 영속성 관련 작업을 해도 테스트 메서드가 종료되기 전에는 커밋이 되지 않는다. 애초에 트랜잭션 맥락이 다르기 때문에 RestAssured로 HTTP 요청을 날리는 시점에는 영속성 작업이 반영돼있지 않은 상태인 것이다.
RestAssured는 @Transactional과 혼용하지 않고 순수하게 엔드포인트 E2E 테스트에만 활용하는 것이 바람직하다고 볼 수 있다.
'Backend' 카테고리의 다른 글
@Param 및 @PathVariable의 파라미터 관련 에러 원인 및 해결방안 (0) | 2025.05.28 |
---|---|
@Embedded, @Embeddable로 객체지향적인 JPA 엔티티 설계하기 (0) | 2025.05.25 |
RestTemplate vs RestClient (0) | 2025.05.21 |
@DataJpaTest 롤백은 id까지 초기화해주지 않는다 (0) | 2025.05.17 |
Spring MVC 동작에 대한 전체적인 복습 (0) | 2025.05.14 |