JPA 에서 벌크 연산을 하는 방법에 대해 알아보겠습니다.
먼저, 벌크 연산은 말 그대로 어떤 조건이나 상황에 따라 한번에 연산(수정) 하는 것을 의미합니다.
가장 중요한 개념은, 벌크 연산 시 SQL 을 실행하는 것이기 때문에 1차 캐시를 거치지 않고 바로 DB 로 접근해서 값을 수정합니다.
따라서, 1차 캐시를 초기화 하지 않으면, 수정한 데이터의 DB 값과 1차 캐시에 있는 값의 정합성이 다르게 됩니다.
예시는 스프링 데이터 JPA 강의 및 QueryDsl 내용을 참고했습니다.
2가지 방법으로 나뉩니다.
스프링 데이터 JPA 를 사용하지 않는 방법과 스프링 데이터 JPA 를 사용한 방법으로 나뉩니다.
먼저 첫번째 스프링 데이터 JPA 를 사용하지 않는 방법입니다.
예시는 정해진 age 보다 크거나 같을 경우 현재 나이에서 1살을 더해주는 것입니다.
jpql 을 사용해서 쿼리를 작성해 줄 수 있습니다.
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();
}
아래는 테스트 코드입니다.
총 5명의 회원이 있는데 조건인 20살보다 많은 사람은 총 3명입니다.
assertThat 으로 확인해보면 테스트가 성공합니다.
@Test
public void bulkUpdate() {
memberJpaRepository.save(new Member("member1", 10));
memberJpaRepository.save(new Member("member3", 19));
memberJpaRepository.save(new Member("member3", 20));
memberJpaRepository.save(new Member("member4", 21));
memberJpaRepository.save(new Member("member5", 40));
int resultCount = memberJpaRepository.bulkAgePlus(20);
assertThat(resultCount).isEqualTo(3);
}
두 번째, 스프링 데이터 JPA 를 사용하는 방법을 확인해보겠습니다.
@Query 와 jpql 을 사용해서 작성해주었습니다.
벌크연산을 진행할때는 @Modifying을 붙여주어야 합니다.
@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
테스트 코드를 진행해보면 정상적으로 성공을 합니다.
@Test
void bulkAgePlus() {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member3", 19));
memberRepository.save(new Member("member3", 20));
memberRepository.save(new Member("member4", 21));
memberRepository.save(new Member("member5", 40));
//when
int resultCount = memberRepository.bulkAgePlus(20);
List<Member> findMember = memberRepository.findByUsername("member5");
Member member = findMember.get(0);
System.out.println("member = " + member);
//then
assertThat(resultCount).isEqualTo(3);
}
데이터베이스를 확인해보겠습니다.
조건에 맞는 나이가 1씩 증가한 것을 확인할 수 있습니다.
이번 테스트 코드에서는 member5 를 find 해서 어떤 값이 나올지 출력해보았는데 어떤 값이 출력될까요?
여기서 DB 와 다른 데이터가 출력됩니다.
출력된 결과를 보면 나이가 40 입니다. DB 와 다른 데이터가 출력되었습니다.
이유가 무엇일까요??
바로 영속성 컨텍스트가 초기화 되지 않았기 때문입니다.
bulkAgePlus 가 실행될 때 벌크연산은 영속성 컨텍스트를 무시하고 DB 값이 바로 수정되어버립니다.
현재 영속성컨텍스트의 member5 의 나이는 40살인데, DB 값은 41살인 상황이 되어버려 서로의 값 상태가 달라진 것 입니다.
이를 해결하기 위해서는 2가지 방법이 존재합니다.
엔티티 매니저를 만들어 flush() 를 해서 DB에 저장을 하고 clear()를 진행해 초기화를 진행합니다.
@PersistenceContext
EntityManager em;
...
int resultCount = memberRepository.bulkAgePlus(20);
em.flush(); // 저장
em.clear(); // 초기화
정상적으로 DB 와 같은 값이 조회되는 것을 볼 수 있습니다.
두번째로, 어노테이션을 이용한 방법입니다.
@Modifying(clearAutomatically=true) 설정을 해주면 동일한 값을 조회할 수 있습니다.
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
'Spring > JPA' 카테고리의 다른 글
OSIV ( Open Session In View ) 에 대해 알아보자. (1) | 2023.03.18 |
---|---|
@queryProjection 이란 ? (0) | 2023.02.24 |
querydsl 다중 조건 검색 만들기 (0) | 2023.02.05 |
Querydsl 동적 쿼리 - 기초 예제 (0) | 2023.02.04 |
QueryDsl 설정 방법 - Spring boot 2.7.x (1) | 2023.02.03 |
댓글