JPA는 엔티티를 가져와서 데이터를 변경하면 트랜잭션 커밋 시점에 변경된 부분을 감지하고 DB에 업데이트 쿼리를 날린다. 이런 경우 데이터 한 건에만 적용된다. 이와 반대로 모든 데이터에 업데이트를 적용하는 쿼리를 벌크성 수정 쿼리라고 한다. (ex: 모든 직원의 연봉을 10% 인상)
SpringDataJPA를 이용한 벌크성 수정 쿼리를 알아보기 전에 먼저 순수 JPA를 이용한 벌크성 수정 쿼리를 알아보고자 한다.
public int bulkAgePlus(int age) {
int resultCount = em.createQuery(
"update Member m set m.age = m.age + 1 where m.age >= :age")
.setParameter("age", age)
.executeUpdate();
return resultCount;
}
@Test
public void bulkUpdate() throws Exception {
//given
memberJpaRepository.save(new Member("member1", 10));
memberJpaRepository.save(new Member("member2", 19));
memberJpaRepository.save(new Member("member3", 20));
memberJpaRepository.save(new Member("member4", 21));
memberJpaRepository.save(new Member("member5", 40));
//when
int resultCount = memberJpaRepository.bulkAgePlus(20);
//then
assertThat(resultCount).isEqualTo(3);
}
SpringDataJPA를 이용하여 벌크성 수정, 삭제 쿼리를 작성하려면 @Modifying 어노테이션을 사용하면 된다.
사용하지 않으면 예외가 발생한다. (org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations)
@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
@Test
public void bulkUpdate() throws Exception {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 19));
memberRepository.save(new Member("member3", 20));
memberRepository.save(new Member("member4", 21));
memberRepository.save(new Member("member5", 40));
//when
int resultCount = memberRepository.bulkAgePlus(20);
//then
assertThat(resultCount).isEqualTo(3);
}
참고: 벌크 연산은 영속성 컨텍스트를 무시하고 실행하기 때문에, 영속성 컨텍스트에 있는 엔티티의 상태와 DB에 엔티티 상태가 달라질 수 있다. 따라서 권장하는 방식은 다음과 같다.
영속성 컨텍스트를 초기화 하는 방법은 2가지가 있다.
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
@PersistenceContext EntityManager em;
@Test
public void bulkUpdate() throws Exception {
...
int resultCount = memberRepository.bulkAgePlus(20);
em.flush();
em.clear();
...
}