jujuwon
시크릿주주
jujuwon
전체 방문자
오늘
어제
  • 분류 전체보기 (106)
    • 🔠 프로그래밍언어 (35)
      • ☕️ Java (19)
      • 🐠 Python (15)
      • 🍠 Kotlin (1)
    • 🔙 Backend (16)
      • 🌿 Springboot (12)
      • 🐳 Docker (1)
      • ☁️ AWS (3)
    • 💼 CS (12)
      • 📶 Network (12)
    • 🕹 알고리즘 (14)
      • 📑 스터디 (2)
      • 💁🏻‍♂️ 백준 (9)
      • 👨🏼‍🔬 프로그래머스 (3)
    • 📚 Book (8)
      • 🔎 오브젝트 (4)
      • 🧪 TDD (2)
      • 📜 논문 (2)
    • 🔐 보안 (7)
      • 👾 Pwnable (7)
    • 📝 회고 (4)
    • 🧩 etc. (10)
      • ⚠️ issue (2)
      • 💡 꿀팁 (7)
      • ✏️ 끄적 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

최근 글

hELLO · Designed By 정상우.
jujuwon

시크릿주주

JPA :: 벌크성 수정 쿼리
🔙 Backend/🌿 Springboot

JPA :: 벌크성 수정 쿼리

2022. 8. 17. 08:43
반응형

벌크 연산


  • 여러 건의 데이터를 한 번에 수정하거나 삭제
  • 조건에 맞는 객체를 다 가지고 와서 수정할 필요 없이 DB 쿼리로 해결하는 것.

 

JPA Bulk


예제 코드 : 파라미터로 받은 나이보다 많은 회원들의 나이를 +1 시킨다.

public int bulkAgePlus(int age) {
    return em.createQuery(
            "update Member m set m.age = m.age + 1"
                + " where m.age >= :age")
        .setParameter("age", age)
        .executeUpdate();
}
  • 테스트 코드
@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", 30));

    //when
    int resultCount = memberJpaRepository.bulkAgePlus(20);

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

테스트 코드를 실행시켜보면 아래와 같이 쿼리가 나간 것을 확인할 수 있다.

DB 에서 확인해보아도 20살 이상의 회원들의 나이가 +1 된 것을 볼 수 있다.

 

Data JPA Bulk


  • 위 예제의 순수 JPA 와 동일한 코드를 Data JPA 에서 작성
@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);

벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 사용 !

사용하지 않으면 QueryExecutionRequestException 발생

  • 테스트 코드는 순수 JPA 와 동일하게 작성

순수 JPA 로 작성했을 때와 동일하게 동작하는 것을 볼 수 있다.

 

문제점


순수 JPA, 데이터 JPA 모두 벌크성 연산을 할 때 문제가 있다.

벌크 연산은 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", 30));

    //when
    int resultCount = memberRepository.bulkAgePlus(20);

    List<Member> result = memberRepository.findByUsername("member5");
    Member member5 = result.get(0); // 이 회원의 나이는
    System.out.println("member5 = " + member5); // 몇살로 출력될까 ??

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

위 테스트 코드에서 bulk 연산을 때리고 난 후 조회한 Member 의 나이는 몇일까 ?

답은 30 이다.

영속성 컨텍스트에는 나이가 30으로 남아있고,

영속성 컨텍스트를 무시한 bulk 연산으로 인해 DB 에는 31살로 반영이 된 것이다.

  • 어떻게 해야 할까 ?
  • bulk 연산 이후 영속성 컨텍스트를 초기화 해야 한다.
@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", 30));

    //when
    int resultCount = memberRepository.bulkAgePlus(20);
    //영속성 컨텍스트 초기화 !
    em.flush();
    em.clear();

    List<Member> result = memberRepository.findByUsername("member5");
    Member member5 = result.get(0); // 이 회원의 나이는
    System.out.println("member5 = " + member5); // 몇살로 출력될까 ??

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

em.flush() 를 통해 새로 save 한 Member 를 DB 에 저장하고

em.clear() 를 통해 초기화해준다.

또는 Data JPA 에 옵션이 있다.

@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);

@Modifying 어노테이션에 clearAutomatically 옵션을 true 로 켜주면

bulk 성 쿼리를 실행하고 나서 영속성 컨텍스트를 초기화 해준다.

 

결론

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

 

권장 방안


  • 영속성 컨텍스트에 엔티티가 없는 상태에서 bulk 연산을 먼저 실행
  • 부득이하게 영속성 컨텍스트에 엔티티가 있으면 bulk 연산 직후 영속성 컨텍스트 초기화
728x90
반응형
저작자표시
    '🔙 Backend/🌿 Springboot' 카테고리의 다른 글
    • Spring Boot :: @FeignClient 로 외부 REST API 간편 호출
    • JUnit :: ParameterizedTest 로 경계값 테스트하기
    • JPA :: 페이징과 정렬
    • JPA :: Query Method 기능
    jujuwon
    jujuwon

    티스토리툴바