JPQL 벌크 연산

PPakSSam·2022년 1월 26일
0
post-thumbnail
post-custom-banner

JPQL 벌크 연산이란 한번에 update, delete하는 연산을 뜻한다.
그렇다면 JPQL 벌크 연산은 갑자기 왜 등장한 것인지 알아보자.

재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?

이것을 우리가 알고 있는 변경 감지 기능으로 실현하려면 답이 없다...

  1. 재고가 10개 미만인 상품을 리스트로 조회한다.

  2. 상품 엔티티의 가격을 10% 증가한다. (for문 돌면서 set 처리...)

  3. 트랜잭션 커밋 시점에 변경감지가 동작한다.

허허... 만약 변경된 데이터가 100건이라면 Update 쿼리가 100번 나가게 된다 ㄷㄷ

이러한 문제를 해결하기 위해 벌크 연산이 필요한 것이다!! 🖐🖐


벌크 연산 예제

String query = "update Product p set p.price = 1.1 * p.price where p.sotckAmount < 10";

// 벌크 연산의 경우 excuteUpdate() 메소드가 필요하다
int resultCount = em.createQuery(query).excuteUpdate();k
  • 쿼리 한번으로 여러 row를 변경한다.

  • excuteUpdate()는 영향받은 엔티티 수를 반환한다.

  • UPDATE, DELETE, INSERT를 지원한다.

  • 하이버네이트의 경우 INSERT INTO ... SELECT 절 또한 지원한다.

벌크 연산시 주의점

Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(10);
member1.setTeam(teamA);
em.persist(member1);

Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(20);
member2.setTeam(teamA);
em.persist(member2);

Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(30);
member3.setTeam(teamB);
em.persist(member3);

String query = "update Member m set m.age = 40";
int resultCount = em.createQuery(query).excuteUpdate(); // (1)
System.out.println("resultCount = " + resultCount); // 3

Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember.getAge() = " + findMember.getAge()); // (2)
  • 벌크 연산을 하면 flush()가 호출된다.
    -> JPQL을 사용하면 flush()가 호출되므로 그렇다.
    -> flush()가 호출되면 쿼리가 나가게 된다.

  • 영속성 컨텍스트 1차 캐시에 있는 엔티티와 DB의 데이터가 안맞는 경우가 발생

  • 영속성 컨텍스트에 저장된 member 1은 age가 10이다.

  • update 쿼리문으로 인해 db에서 age는 모두 40으로 업데이트

  • 그러나 find해서 가져온 객체는 영속성 컨텍스트에 저장된 객체이므로 age가 10이다.

객체의 상태와 DB의 상태의 일관성이 깨져버린다!!


해결 방법

해결방법에는 2가지가 있다.

  1. 벌크연산을 먼저 실행한다.

  2. 벌크 연산 수행 후 영속성 컨텍스트를 초기화한다 (추천)

Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(10);
member1.setTeam(teamA);
em.persist(member1);

Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(20);
member2.setTeam(teamA);
em.persist(member2);

Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(30);
member3.setTeam(teamB);
em.persist(member3);

String query = "update Member m set m.age = 40";
int resultCount = em.createQuery(query).executeUpdate();
System.out.println("resultCount = " + resultCount);

em.clear(); // 영속성 컨텍스트 초기화

Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember.getAge() = " + findMember.getAge()); // 40
  • 영속성 컨텍스트를 초기화 했으므로 find한 객체는 DB를 조회한 객체이다.

  • 그러므로 update 쿼리가 반영된 객체를 가져오므로 해결된다 굿굿

profile
성장에 대한 경험을 공유하고픈 자발적 경험주의자
post-custom-banner

0개의 댓글