스프링 데이터 JPA - 벌크성 수정 쿼리
JPA를 사용한 벌크성 수정 쿼리
JPA에서 전체 회원의 나이를 바꿔야 한다면?
- 20살 이상의 회원의 나이를 + 1 해라
순수 JPA
1
2
3
4
5
6
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();
}
다음과 같이 age를 받아서 age 이상인 것만 +1 업데이트 문을 날리도록 만들었다.
테스트 코드를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void bulkUpdate() throws Exception {
//given
memberJpaRepository.save(new Member("member1", 10));
memberJpaRepository.save(new Member("member2", 12));
memberJpaRepository.save(new Member("member3", 20));
memberJpaRepository.save(new Member("member4", 25));
memberJpaRepository.save(new Member("member5", 30));
memberJpaRepository.save(new Member("member6", 50));
//when
int result = memberJpaRepository.bulkAgePlus(20);
//then
assertThat(result).isEqualTo(4);
}
멤버를 여러 개 만들고 20을 했더니 결과가 4가 나왔고 통과가 잘 됐다.
DB에서 확인해보면

원하는 대로 +1이 된 걸 볼 수 있다.
이제 스프링 데이터 JPA에서 어떻게 하는 지 알아보자.
스프링 데이터 JPA
스프링 데이터 JPA에서도 @Query어노테이션을 이용하여 순수 JPA에서 사용한 쿼리를 그대로 사용한다.
1
2
3
@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
다음과 같이 age를 @Param으로 잡고, @Query에 쿼리를 작성했다.
그리고 추가 된 것이 @Modifying어노테이션이다. 수정 작업이 있다면 반드시 줘야 한다.
순수 JPA에서도
.executeUpdate();한 것과 비슷하다고 보면 된다.
이제 테스트 코드를 순수 JPA와 같이 사용해 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void bulkUpdate() throws Exception {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 12));
memberRepository.save(new Member("member3", 20));
memberRepository.save(new Member("member4", 25));
memberRepository.save(new Member("member5", 30));
memberRepository.save(new Member("member6", 50));
//when
int result = memberRepository.bulkAgePlus(20);
//then
assertThat(result).isEqualTo(4);
}
다음과 같이 했고 통과도 잘 됐고 결과도

다음과 같이 잘 나왔다.
그런데 벌크 업데이트에선 주의해야 할 게 있다. 벌크 업데이트는 DB에 직접 업데이트를 치는 것이므로 영속성 컨텍스트와 무관하게 동작한다.
벌크 업데이트 주의 사항
자 예를 들어 보겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void bulkUpdate() throws Exception {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 12));
memberRepository.save(new Member("member3", 20));
memberRepository.save(new Member("member4", 25));
memberRepository.save(new Member("member5", 30));
memberRepository.save(new Member("member6", 50));
//when
int result = memberRepository.bulkAgePlus(20);
Member findMember = memberRepository.findById(6L).get();
System.out.println("findMember = " + findMember);
//then
assertThat(result).isEqualTo(4);
}
이런 코드가 있을 때 과연 member6의 나이가 50일까 51일까?

정답은 50이다.
벌크 연산 자체가 영속성 컨텍스트를 건드리지 않고 직접 DB에 업데이트를 친 행위 이고,
그 전에 memberRepository.save(new Member("member6", 50)); 하면서 1차 캐시에 이 값을 저장해 놓았기 때문에
1
2
Member findMember = memberRepository.findById(6L).get();
System.out.println("findMember = " + findMember);
다음과 같은 행위를 했을 때 DB를 거치지 않고 1차 캐시에서 값을 가져오게 된 것이다.
그러므로 반드시 벌크 업데이트 작업이 끝난 후에 엔티티 매니져를 날려 줘야 한다. 이와 마찬가지로 MyBatis나 JDBC템플릿을 섞어서 사용한다면 이 또한 JPA는 인식하지 못한다.
1 2 em.flush(); em.clear();이 두 코드를 추가 하고 다시 해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void bulkUpdate() throws Exception {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 12));
memberRepository.save(new Member("member3", 20));
memberRepository.save(new Member("member4", 25));
memberRepository.save(new Member("member5", 30));
memberRepository.save(new Member("member6", 50));
//when
int result = memberRepository.bulkAgePlus(20);
em.flush();
em.clear();
Member findMember = memberRepository.findById(6L).get();
System.out.println("findMember = " + findMember);
//then
assertThat(result).isEqualTo(4);
}
실행 해보면
다음과 같이 51살로 바뀐 걸 볼 수 있다.
또 스프링 데이터 JPA에서는 em.flush(), clear()할 필요 없이 @Modifying어노테이션에 기능이 있다.
1
2
3
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
다음과 같이 clearAutomatically = true를 넣어주면 엔티티 매니저를 대신 날려준다.
댓글남기기