JPQL 벌크 연산이란 한번에 update, delete하는 연산을 뜻한다.
그렇다면 JPQL 벌크 연산은 갑자기 왜 등장한 것인지 알아보자.
재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
이것을 우리가 알고 있는 변경 감지 기능으로 실현하려면 답이 없다...
재고가 10개 미만인 상품을 리스트로 조회한다.
상품 엔티티의 가격을 10% 증가한다. (for문 돌면서 set 처리...)
트랜잭션 커밋 시점에 변경감지가 동작한다.
허허... 만약 변경된 데이터가 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가지가 있다.
벌크연산을 먼저 실행한다.
벌크 연산 수행 후 영속성 컨텍스트를 초기화한다 (추천)
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 쿼리가 반영된 객체를 가져오므로 해결된다 굿굿