[JPA] 벌크 연산

유승욱·2024년 2월 3일
0

💡벌크 연산이란?

기본적으로 update를 하기 위해서 JPA에서는 더티 체킹 기능을 제공한다. 트랜잭션 내에서 필드의 변경이 일어나면 해당 변경을 트랜잭션 커밋 시점에 체크해서 update 쿼리를 날리는 것이다. 하지만 N개의 엔티티의 필드 값을 변경하면 더티 체킹 연산 역시 N번 일어나게 되어 연산의 숫자가 많아지면 비효율적으로 변한다.

따라서 아래의 코드와 같이 더티체킹을 하지 않고 update 쿼리를 날려 필드 값을 변경하는 것이 효율적일것이다.
이렇게 여러 건의 데이터를 한 번에 수정하는 것을 '벌크 연산'이라고 한다.

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

벌크 연산 방법

특정 나이 이상의 멤버의 나이 값을 1씩 증가시킨다고 가정해보자. 그렇다면 Spring Data Jpa로 다음과 같은 코드를 작성할 수 있다.

@Modifying() // executeUpdate()로 실행시켜준다.
    @Query("update Member m set m.age = m.age + 1 where m.age >= :age")
    int bulkAgePlus(@Param("age") int age);

이때 @Modifying() 에노테이션을 작성해주지 않으면 벌크 연산이 제대로 수행되지 않는다.

벌크 연산의 문제점

벌크 연산의 문제는 DB에 바로 쿼리를 쏜다는 것이다.
그렇기 때문에 영속성 컨텍스트(1차 캐시)를 무시하고 DB에 쿼리를 날리기 때문에 DB와 영속성 컨텍스트의 불일치가 발생한다.

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

        int resultCount = memberRepository.bulkAgePlus(20);

        List<Member> result = memberRepository.findByUsername("member5");
        Member member5 = result.get(0);
        System.out.println("member5 = " + member5);

        assertThat(resultCount).isEqualTo(3);
    }

벌크 연산을 통해 age가 20 이상인 멤버의 age가 1씩 증가되어 DB에 저장되었지만, 영속성 컨텍스트에는 변경 사항이 반영이 되지 않았기 때문에 아래와 같이 기존의 값이 그대로 출력되게 된다.

해결 방법

@Modifying 에노테이션에 clearAutomatically = true를 추가해주면 된다.

@Modifying(clearAutomatically = true) // executeUpdate()로 실행시켜준다.
   @Query("update Member m set m.age = m.age + 1 where m.age >= :age")
   int bulkAgePlus(@Param("age") int age);


제대로 변경된 것을 확인할 수 있다.

0개의 댓글