반응형
벌크 연산
- 여러 건의 데이터를 한 번에 수정하거나 삭제
- 조건에 맞는 객체를 다 가지고 와서 수정할 필요 없이 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
반응형