자바생
article thumbnail
Published 2022. 4. 12. 01:27
벌크 연산 Spring 강의/Spring data JPA
728x90

순수 JPA 사용

 

조건에 맞는 엔티티들의 값을 일괄 수정하고 싶다면 벌크 연산을 사용하면 된다

public List<Member> bulkAgePlus(int age) {
    return em.createQuery("update Member m set m.age = m.age + 1 where m.age >= : age", Member.class)
            .setParameter("age", age)
            .getResultList();
}

public List<Member> bulkAgePlus(int age) {
    return em.createQuery("update Member m set m.age = m.age + 1 where m.age >= : age")
            .setParameter("age", age)
            .getResultList();
}

위와 같이 코드를 작성하고 테스트를 돌려보자

@Test
void bulkUpdate() throws Exception {
    //given
    memberJpaRepository.save(new Member("member1", 1));
    memberJpaRepository.save(new Member("member2", 2));
    memberJpaRepository.save(new Member("member3", 3));
    memberJpaRepository.save(new Member("member4", 4));
    memberJpaRepository.save(new Member("member5", 5));

    //when
    List<Member> members = memberJpaRepository.bulkAgePlus(3);

    for (Member member : members) {
        System.out.println("member = " + member);
    }
}

update나 delete 쿼리는 타입으로 반환될 수 없고, 두번째 코드에서는 해당 DML을 지원하지 않는다고 한다

 

그래서 아래의 코드와 같이 executeUpdate 메서드를 이용하여 벌크 연산을 수행해야한다

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();
}

테스트 코드를 돌리고 DB를 보면 age가 3이상인 엔티티들의 age가 1만큼 늘어났다

 

executeUpdate()

  • update나 delete statement를 execute하는 메서드
  • 삭제되거나 업데이트 된 엔티티의 개수를 반환한다

 

Spring data JPA 사용

 

@Test
void bulkUpdate() throws Exception {
    //given
    memberRepository.save(new Member("member1", 1));
    memberRepository.save(new Member("member2", 2));
    memberRepository.save(new Member("member3", 3));
    memberRepository.save(new Member("member4", 4));
    memberRepository.save(new Member("member5", 5));

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

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

 

@Modifying이 없을 경우

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

위와 같은 에러가 발생한다

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

 

@Modifying?

  • @Query 주석을 통해 정의된 쿼리 메서드에 사용되는 경우에만 사용
  • INSERT, UPDATE, DELETE 및 DDL 문에서 필요
@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
List<Member> bulkAgePlus(@Param("age") int age);
궁금해서 반환형을 int가 아닌 List<Member>로 변경했으나 위와 똑같은 에러 발생

 

벌크 연산 시 조심해야할 점

 

@Test
void bulkUpdate() throws Exception {
    //given
    memberRepository.save(new Member("member1", 1));
    memberRepository.save(new Member("member2", 2));
    memberRepository.save(new Member("member3", 3));
    memberRepository.save(new Member("member4", 4));
    memberRepository.save(new Member("member5", 5));

    //when
    int totalCount = memberRepository.bulkAgePlus(3);

    List<Member> members = memberRepository.findByUsername("member3");
    Member member = members.get(0);
    System.out.println("member = " + member);

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

 

출력한 member3의 age와 DB에 저장된 age는 왜 다를까?

 

DB에 저장된 member3의 age는 4, 출력한 age는 3이 된다

 

  • 결론을 말하자면 bulkAgePlus 쿼리 메서드는 DB에 직접 쿼리를 날리는 것이다
    • 즉, member들은 영속성 컨텍스트에 의해 관리되는데 벌크 연산은 그걸 무시하고 바로 DB에 쿼리를 날린다
    • 따라서 영속성 컨텍스트에 존재하는 엔티티의 age는 update 되지 않는다
    • flush를 해주어야 값이 변하게 된다

영속성 컨텍스트를 clear 해주면 age 값을 출력할 때 4를 볼 수 있다

 

그렇다면 벌크 연산을 실행할 때마다 항상 EntityManager를 주입 받아야할까?

 

그렇지 않다

  • @Modifying에 clearAutomatically 옵션 true 설정
  • 수정 쿼리를 실행한 후 영속성 컨텍스트를 clear 해준다
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);

 


REFERENCES

스프링 데이터 JPA (김영한 님)

 

728x90

'Spring 강의 > Spring data JPA' 카테고리의 다른 글

@EntityGraph  (0) 2022.04.13
페이징과 정렬  (0) 2022.04.11
쿼리 메서드  (0) 2022.04.08
profile

자바생

@자바생

틀린 부분이 있다면 댓글 부탁드립니다~😀

검색 태그