본문 바로가기

JPA

[Spring Data JPA] 벌크성 수정 쿼리

벌크성 쿼리

벌크성 쿼리(Bulk Query)란 데이터베이스에서 한 번의 실행으로 대량의 데이터를 삽입, 수정, 삭제하거나 조회하는 쿼리를 의미한다.

주로 많은 데이터를 효율적으로 처리하기 위해 사용되며, 데이터 처리 속도를 높이고 시스템 리소스를 절약하는 데 유용하다.

예시로 모든 직원의 연봉을 10% 인상하는 쿼리를 짜야 한다면, 직원들을 하나씩 끌어서 10% 씩 인상하는 것보다는 DB에서 업데이트 쿼리를 한번만에 끝내고 커밋하는 것이 효율적일 것이다. 먼저 순수 JPA를 사용하여 벌크성 수정 쿼리를 처리하는 방법을 알아보자.

 

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;
}
  • 회원의 나이가 파라미터로 들어온 값보다 크거나 같다면 나이에 + 1를 하는 벌크성 쿼리이다.
  • executeUpdate() - 반환 값이 int 이다.

JPA를 사용한 벌크성 수정 쿼리 테스트

@Test
public void bulkUpdate() {
    // 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);
}

 

 

스프링 데이터 JPA를 사용한 벌크성 수정 쿼리

@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
  • org.springframework.data.jpa.repository.Modifying;
  • 벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 사용해야 한다.

  • 이 어노테이션이 있어야 JPA에서 executeUpdate()를 실행한다.
  • 이게 없으면 getResultList()나 getSingleResult()를 호출해버리기 때문이다.

사용하지 않으면 다음 예외 발생

java.lang.IllegalStateException: Query executed via 'getResultList()' or 'getSingleResult()' must be a 'select' query [update Member m set m.age = m.age + 1 where m.age >= :age]
	at org.hibernate.query.sqm.internal.QuerySqmImpl.verifySelect(QuerySqmImpl.java:352)
	at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:372)
	at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143)
	at org.hibernate.query.spi.AbstractSelectionQuery.getSingleResult(AbstractSelectionQuery.java:275)

 

스프링 데이터 JPA를 사용한 벌크성 수정 쿼리 테스트

@Test
public void bulkUpdate() {
    // 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);
    // em.clear(); or @Modifying(clearAutomatically = true)

    // 영속성 컨텍스트를 무시하고 실행
    List<Member> result = memberRepository.findByUsername("member5");
    Member member5 = result.get(0);
    System.out.println("member5 = " + member5); // 40살

    // then
    assertThat(resultCount).isEqualTo(3);
}

 

주의점

  • 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트 초기화: @Modifying(clearAutomatically = true) (이 옵션의 기본값은 false)
  • 이 옵션없이 회원을 findById 로 다시 조회하면 영속성 컨텍스트에 과거 값이 남아서 문제가 될수있다.
  • 만약 다시 조회해야 하면 꼭 영속성 컨텍스트를 초기화 하자.

벌크 연산은 영속성 컨텍스트를 무시하고 실행하기 때문에, 영속성 컨텍스트에 있는 엔티티의 상태와 DB에 엔티티 상태가 달라질 수 있다.


권장하는 방안

  1. 영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산을 먼저 실행한다.
  2. 부득이하게 영속성 컨텍스트에 엔티티가 있으면 벌크 연산 직후 영속성 컨텍스트를 초기화 한다.

'JPA' 카테고리의 다른 글

[Spring Data JPA] JPA Hint & Lock  (0) 2025.01.18
[Spring Data JPA] @EntityGraph  (0) 2025.01.17
[Spring Data JPA] 페이징과 정렬  (0) 2025.01.16
[JPA] OSIV와 성능 최적화  (0) 2025.01.10
[JPA] 컬렉션 조회 최적화  (0) 2025.01.10