zini's blog

CRUD - UPDATE 방법 비교 본문

프로젝트/하우키키 : 고객 응대 챗봇

CRUD - UPDATE 방법 비교

yjzini 2025. 1. 30. 14:17

엔티티 조회 후 수정 vs 쿼리문으로 수정

  • 기본적인 crud 중 수정시, 엔티티 조회를 통해 객체를 가져와 수정하는 방법과, 쿼리문을 통해 update하는 방법의 차이가 뭘까? 그리고 어떤 방법을 사용해야 할까?

(공부하며 작성한 글이라 틀린 부분이 있을 수 있습니다!)


 

1. 엔티티 조회 후 수정 (Dirty Checking)

📌 작동 방식 - (@Transactional사용한 경우로 설명)

  • 엔티티를 DB에서 조회한 후, 해당 엔티티의 값을 변경함.
  • JPA의 Dirty Checking(변경 감지)을 통해 트랜잭션 커밋 시점에 수정된 내용을 DB에 반영함.

🧩 코드 예시

@Transactional
public void updateOrder(Long orderId) {
	// 엔티티 조회
    Order order = orderRepository.findById(orderId).orElseThrow();

     // 상태 업데이트
    order.updateStatus(USER_CANCELLED);

    // 명시적으로 save 호출 (선택사항- 안써도 영속성 컨택스트에 의해 저장됨)
    Order modifiedOrder = orderRepository.save(order);
    
    // 결과 반환
    return OrderResponseDto.fromWithoutOrderDetail(modifiedOrder);
}

 

⚙️ 작동 과정 정리

  1. 엔티티 조회 (findById)
    • orderRepository.findById(orderId)로 DB에서 Order 엔티티를 가져옴.
    • 이때 JPA는 *영속성 컨텍스트(Persistence Context)에 엔티티를 관리 상태로 등록
  2. 엔티티 값 변경 (updateStatus)
    • order.updateStatus(USER_CANCELLED); 호출 시 객체의 필드 값이 메모리에서 변경됨.
    • 이때 아직 데이터베이스에는 아무 변화 없음!
  3. 변경 감지 (Dirty Checking)
    • 트랜잭션이 끝나기 전에 JPA가 *영속성 컨텍스트에서 관리 중인 엔티티의 변경 내용을 감지
    • 변경된 필드를 추적하여 적절한 UPDATE 쿼리를 생성
  4. 저장 (save)
    • orderRepository.save(order); 호출은 사실 선택 사항.
    • 이미 영속성 컨텍스트에서 엔티티가 관리되고 있다면 save 없이도 변경 내용이 반영됨
    • 다만 save() 호출은 코드를 읽는 사람에게 "변경이 끝났으니 저장하겠다"는 명확한 의도를 전달하는 역할이 될 수 있음
  5. 트랜잭션 종료 및 커밋
    • 메서드가 끝나면서 트랜잭션이 종료되고 커밋이 발생.
    • JPA는 이때 변경 내용을 DB에 반영하기 위해 UPDATE 쿼리를 실행.

🔎 내부에서 발생하는 DB 쿼리 흐름

SELECT * FROM orders WHERE order_id = ?
UPDATE orders SET status = ? WHERE order_id = ?

 

장점

  • 객체 지향적 코드 작성 가능
  • 여러 값 변경 시 하나의 트랜잭션으로 관리 가능
  • 영속성 컨텍스트 활용으로 최적화 (변경 사항만 쿼리 전송)

단점

  • 엔티티 조회 과정 필요 → 성능 이슈
  • 데이터가 많아질수록 메모리 사용량 증가
  • 복잡한 쿼리 작업에는 비효율적

2. 쿼리문으로 수정 (@Modifying + @Query)

📌 작동 방식

  • JPA Repository에서 직접 JPQL 또는 Native Query를 사용해 즉시 DB 데이터를 수정함.

🧩 코드 예시 - Repository

@Transactional
@Modifying
@Query("UPDATE Order o SET o.status = :status WHERE o.orderId = :orderId")
public void updateOrderStatus(@Param("orderId") Long orderId, @Param("status") String status);

 

⚙️ 작동 과정

  1. 쿼리가 작성된 메서드 호출 시 DB로 즉시 쿼리 전송
    • @Modifying + @Query 메서드는 실행 즉시 쿼리가 DB로 전송되어 데이터가 변경됨
  2. DB 수정 작업 즉시 실행
  3. 트랜잭션 커밋 시 변경 사항 유지 (메서드 실패시 - 롤백되어 취소됨)

장점

  • 엔티티 조회 과정 없이 바로 DB 수정 가능 → 성능 향상
  • 대량 데이터 업데이트에 유리
  • 복잡한 조건으로 쿼리 작성 가능

단점

  • 객체 지향적인 코드 작성 어려움
  • 영속성 컨텍스트와 동기화되지 않음 → 데이터 일관성 문제 가능성
  • 쿼리 작성의 복잡성 증가

 

cf) 트랜잭션(@Transactional)과 save()의 관계

1. 트랜잭션(@Transactional)의 역할

  • 트랜잭션이 있어야 JPA가 *변경 감지(Dirty Checking)를 수행하고, 트랜잭션이 커밋될 때 변경 내용을 DB에 반영
  • 트랜잭션이 없으면 메서드가 끝나도 JPA가 데이터를 실제로 DB에 반영할 기회가 없음.
  • 트랜잭션과 커밋 시점 정리
    1. 트랜잭션 시작 (@Transactional)
      • 메서드가 호출되면 트랜잭션이 시작됨. 이때 데이터베이스에 반영되지 않고 메모리(트랜잭션 컨텍스트)에서 모든 작업이 대기함.
    2. 쿼리 실행 (Dirty Checking)
      • Dirty Checking: 엔티티 변경은 트랜잭션 종료 직전까지 메모리에서 감지되며 최종적으로 쿼리를 생성
    3. 커밋 시점 (트랜잭션 성공 시)
      • 메서드가 정상적으로 종료되면 커밋(Commit) 이 발생하며 DB에 변경 사항이 확정됨.
      • 이때 이전에 실행된 쿼리(업데이트) 유지됨.
    4. 롤백 (트랜잭션 실패 시)
      • 메서드 실행 중 예외가 발생하면 롤백(Rollback)됨.
      • 이미 실행된 쿼리(UPDATE, DELETE 등)도 모두 취소됨.

2. save() 메서드의 역할

  • save()를 호출하면 엔티티가 즉시 영속성 컨텍스트에 반영되며, DB에 INSERT 또는 UPDATE 쿼리가 실행됨.
  • 트랜잭션이 없더라도 save는 명시적으로 DB 커넥션을 통해 작업을 요청함.

3. 결론: 트랜잭션과 save()의 동작

트랜잭션 save() 호출 DB 반영 여부
❌ 없음 ❌ 호출 안 함 🚫 반영 안 됨
❌ 없음 ✅ 호출 ✅ 반영됨
✅ 있음 ❌ 호출 안 함 ✅ Dirty Checking 반영됨
✅ 있음 ✅ 호출 ✅ 반영됨
  • @Transactional 없이도 save()가 있다면 DB에 반영된다.
  • 하지만 Dirty Checking은 @Transactional이 있어야만 작동한다.
  • 따라서 실무에서는 @Transactional을 붙이는 것이 권장되며, 명시적인 save() 호출은 유지 보수에 따라 적절히 선택하는 것이 좋다!

🔄 비교 요약

항목 엔티티 조회 후 수정 (with @Transactional) 쿼리문으로 수정
작동 방식 Dirty Checking 즉시 쿼리 실행
엔티티 조회 필요 여부 필요 불필요
DB 쿼리 전송 시점 트랜잭션 커밋 시 메서드 실행 즉시
성능 데이터 많을 때 비효율적 성능 우수
복잡한 조건 처리 어려움 용이
트랜잭션 커밋 영향 커밋 성공 시 변경 적용 동일
객체 지향 코드 작성 용이 어려움

 

🚀 결론

  • 단순 수정이나 엔티티 간 연관성 유지가 필요한 경우엔티티 조회 후 수정(Dirty Checking)
  • 대량 데이터 수정 또는 복잡한 조건 기반 작업쿼리문으로 수정