✏️ [JPQL] 벌크 연산

박상민·2023년 11월 2일
0

JPA

목록 보기
21/24
post-thumbnail

⭐️ 벌크 연산

엔티티를 수정하려면 영속성 컨텍스트의 변경 감지 기능이나 병합을 사용하고, 삭제하려면 엔티티 매니저의 remove() 메서드를 사용한다.

하지만, 이러한 방법으로 수많은 엔티티를 하나씩 처리하기에는 시간이 오래 걸릴 것이다.


예를 들어, 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려는 상황이다.

  • (1) 재고가 10개 미만인 상품을 리스트로 조회한다.
  • (2) 상품 엔티티의 가격을 10% 증가한다.
  • (3) 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)가 동작한다.

이 경우, JPA 변경 감지 기능(Dirty Checking)으로 실행하려면 너무 많은 SQL이 실행된다.

변경된 데이터가 100건이라면 100번의 UPDATE SQL이 실행될 것이다.

이러한 문제를 해결하기 위해 한 번에 수정하거나 삭제하는 벌크 연산을 사용한다.

벌크 연산 예제

String qlString = "update Product p " +
				  "set p.price = p.price * 1.1 " +
                  "where p.stockAmount < :stockAmount";

int resultCount = em.createQuery(qlString)
				    .setParameter("stockAmount", 10)
                    .executeUpdate();

실사용 예

em.createQuery("update Member m set m.age = 20")
        .executeUpdate();

System.out.println("resultCount = " + resultCount);
  • 쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
  • executeUpdate()의 결과는 영향받은 엔티티 수를 반환
  • UPDATE, DELETE 지원
  • INSERT(insert into … select, 하이버네이트 지원)

📌 벌크 연산 주의사항(문제점)

  • 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리한다.
  • 영속성 컨텍스트를 무시하기 때문에 발생할 수 있는 문제점 존재한다.
String query = "select p from Product p where p.name = :name";

// (1) 상품A 조회(상품 A의 가격은 1000원으로 가정)
Product productA = em.createQuery(query, Product.class)
        .setParameter("name", "productA")
        .getSingleResult();

// 1000 출력
System.out.println("productA 수정 전 = " + productA.getPrice());

// (2) 벌크 연산 수행으로 모든 상품 가격 10% 상승
em.createQuery("update Product p set p.price = p.price * 1.1")
        executeUpdate();

// (3) 1000 출력, 문제 발생!
System.out.println("productA 수정 후 = " + productA.getPrice());
  • (1) 가격이 1000원인 상품 A를 조회하여 상품 A는 영속성 컨텍스트에서 관리한다.

  • (2) 벌크 연산으로 모든 상품의 가격을 10% 상승시켰기 때문에 상품 A의 가격은 1100으로 기대한다.

  • (3) 하지만, 출력 결과는 기대했던 1100이 아닌 1000이 출력된다.

벌크 연산은 데이터베이스에 직접 쿼리하기 때문에 변경사항이 DB에는 적용이 되지만 영속성 컨텍스트에는 적용이 안된다.
3번 과정에서 productA.getPrice()는 영속성 컨텍스트에 남아있는 productA에서 데이터를 가져오기 때문에 상품 가격 10% 상승이 적용안된 1000원을 출력한다.

📌 벌크 연산 문제점 해결 방법

벌크 연산은 영속성 컨텍스트를 통하지 않고 데이터베이스에 직접 쿼리하기 때문에, 영속성 컨텍스트에 있는 데이터와 데이터베이스에 있는 데이터가 달라질 수 있다.

이러한 문제를 해결하기 위한 3가지 방법이 있다.

✔︎ em.refresh() 사용

벌크 연산을 수행한 직후에 상품 A 엔티티를 사용해야 하는 경우 em.refresh() 메서드를 사용하여 데이터베이스에서 상품 A를 다시 조회한다.

✔︎ 벌크 연산 먼저 실행 - 가장 실용적인 해결책

가장 실용적인 해결책
가장 실용적인 해결책으로 벌크 연산을 가장 먼저 실행하는 방법이다.
벌크 연산을 먼저 실행하고 나서 상품 A를 조회하면 벌크 연산으로 변경된 상품A를 조회할 수 있다.
이 방법은 JPA와 JDBC를 함께 사용할 때에도 유용하다.

✔︎ 벌크 연산 수행 후 영속성 컨텍스트 초기화

벌크 연산을 수행한 직후에 바로 영속성 컨텍스트를 초기화하여 남아있는 엔티티를 제거한다.
영속성 컨텍스트를 초기화하지 않으면 엔티티를 조회할 때 벌크 연산이 적용되지 않은 엔티티를 조회할 수 있다.
영속성 컨텍스트를 초기화하면 이후 엔티티를 조회할 때 벌크 연산이 적용된 데이터베이스에서 엔티티를 조회한다.

위 방법들 중에서 가능하면 벌크 연산을 가장 먼저 수행하는 방법을 사용하는 것이 좋다.

상황에 따라 영속성 컨텍스트를 초기화하는 것도 필요하다.


출처
자바 ORM 표준 JPA 프로그래밍 강의
게시글 속 자료는 모두 위 강의 속 자료를 사용했습니다.

profile
스프링 백엔드를 공부중인 대학생입니다!

1개의 댓글

comment-user-thumbnail
2024년 1월 9일

근데 벌크업은 좀 하셔야될듯,,

답글 달기