솔솔
[JPA] JPA에서 @Transactional 없으면 UPDATE가 실행되지 않는 이유 본문
🍀 들어가기
아래와 같은 코드로 테스트를 진행을 하는데
// Test 코드
@SpringBootTest
class OrderServiceTest {
@Autowired
private StockRepository stockRepository;
@Autowired
private OrderService orderService;
@DisplayName("재고와 관련된 상품이 포함되어 있는 주문번호 리스트를 받아 주문을 생성한다")
@Test
void createOderWithStock() {
...(생략)
stockRepository.save(stock);
orderService.createOrder(request)
...(생략)
<내가 생각한 Stock의 quantity와 니가 수행한 Stock의 quantity가 같니??>
}
}
테스트가 실패했다.
왜 실패했는지 찾아보니
// 서비스 코드
@RequiredArgsConstructor
@Service
public class OrderService {
private final StockRepository stockRepository;
publci OrderResponse createOrder(OrderCreateRequest request){
...생략
// Stock 엔티티에서 quantity만큼 재고를 감소 시키는 메소드
stock.deductQuantity(quantity);
...생략
}
}
// Stock 엔티티 코드
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Stock extends BaseEntity {
...생략
// Stock의 재고를 감소시키는 메소드
public void deductQuantity(int quantity) {
if (isQuantityLessThan(quantity)) {
throw new IllegalArgumentException("차감할 재고 수량이 없습니다.");
}
this.quantity -= quantity;
}
}
stock1.deductQuantity(quantity);가 엔티티의 재고를 감소시키는 메서드라면,
변경 감지(Dirty Checking)가 작동해야 UPDATE 쿼리가 발생하고 재고가 감소하는데
변경 감지(Dirty Checking)가 작동하지 않아 UPDATE 쿼리가 발생하지 않았기 때문이다.
즉 stock.deductQuantity(quantity);을 호출하면 객체의 필드 값이 변경되지만,
변경된 상태가 영속성 컨텍스트에 반영되지 않았던 것이다.
여기서 궁금했다.
왜 변경 감지가 일어나지 않았을까?
🍀 JPA에서 @Transactional 없으면 UPDATE가 실행되지 않는 이유
결론적으로 JPA를 사용하면서 변경 감지(Dirty Checking)를 작동시켜 UPDATE 쿼리를 발생시킬려면
Business Layer 또는 Test 코드 쪽에 @Transcational 어노테이션을 달아놓은 후
// 서비스 코드
@RequiredArgsConstructor
@Transactional
@Service
public class OrderService {
...생략
}
// Test 코드
@Transactional
@SpringBootTest
class OrderServiceTest {
...생략
}
다시 테스트를 해보면 변경 감지(Dirty Checking)가 작동하면서 UPDATE 쿼리가 발생하고
테스트가 성공한다.
🤔 근데 여기서 왜 @Transcational이 없이 UPDATE가 실행되지 않을까?
Spring Data JPA에서는 엔티티 상태를 추적하여 변경된 내용만을 DB에 반영하는 Dirty Checking 기능을 제공한다.
하지만 이 기능은 트랜잭션 범위 내에서만 동작한다.
만약 @Transactional 없이 엔티티를 수정하면, 트랜잭션이 관리되지 않기 때문에 변경 사항이 DB에 반영되지 않는다.
즉 @Transactional이 트랜잭션의 범위를 설정하고 영속성 컨텍스트의 상태를 관리한다!!
✅ JPA에서의 Dirty Checking 동작 원리
@Transactional이 붙은 메서드에서는 JPA의 영속성 컨텍스트(Persistence Context)가 활성화되고, 이 컨텍스트는 엔티티의 상태를 추적합니다. 엔티티가 수정되면, 영속성 컨텍스트가 그 변경을 감지하고, 트랜잭션이 커밋될 때 해당 변경을 DB에 반영하는 Dirty Checking이 이루어진다.
// 서비스 코드
@RequiredArgsConstructor
@Transactional
@Service
public class OrderService {
private final StockRepository stockRepository;
publci OrderResponse createOrder(OrderCreateRequest request){
...생략
stock.deductQuantity(quantity); // ✅ Dirty Checking으로 변경 감지됨
// @Transactional 덕분에 트랜잭션이 끝날 때 자동으로 UPDATE 실행됨
...생략
}
}
🍀 @Transactional 없이도 UPDATE 쿼리를 실행하려면?
@Transactional 없이 엔티티를 수정하고 DB에 반영하려면
명시적으로 save() 메서드를 호출하여 엔티티 상태를 영속성 컨텍스트에 반영해야 함.
// 서비스 코드
@RequiredArgsConstructor
@Service
public class OrderService {
private final StockRepository stockRepository;
publci OrderResponse createOrder(OrderCreateRequest request){
...생략
stock.deductQuantity(quantity);
stockRepository.save(stock); // 명시적으로 save() 호출하여 변경 사항을 DB에 반영
...생략
}
}
JPA는 save()를 호출할 때 내부적으로 UPDATE 쿼리를 생성하고 실행하기 때문에,
@Transactional 없이도 엔티티의 변경을 DB에 반영할 수 있다.
그런데 말입니다..
StockRepository에서 상속받은 JpaRepository를 쭉쭉 타고올라가다보면
save()가 있는 CrudRepository가 있고
save()의 구현체를 살펴 보면
@Transactional이 있는 것을 확인 할 수 있다.
결론적으로 save()를 호출할 때 내부적으로 UPDATE 쿼리를 생성하고 실행할 수 있는건
@Transactional이 있었기 때문이다.
'나의보물들 > JPA' 카테고리의 다른 글
[JPA] @Modifying과 @Transactional의 연관성 (0) | 2024.08.12 |
---|---|
[JPA] 트러블 슈팅 : build.gradle 설정 오류 (0) | 2024.08.10 |
[JPA] JPA란? (0) | 2024.08.01 |