우리가 기존 수정은 데이터를 가져온 다음,
그걸 트랜잭션 내부에서 엔티티의 값을 변경을 하면 알아서 update 쿼리가 commit 시점에 나간다.
근데, 만약 예를 들어 전체 회사원의 연봉을 10% 올리고 싶다면?
이렇게 한번에 bulk(통으로)적으로 수정하는 걸 벌크성 수정 쿼리라고 한다.
먼저 순수 JPA에서
public int bulkAgePlus(){
return em.createQuery("update Member m set m.age= m.age + 3")
.executeUpdate();
}
그냥 통으로 update 시켜 버리면 됨.
조건 넣고 싶을 경우 인자로 받던지 해서 넣으면 되고,
그리고 나름 중요한 부분이, executeUpdate() 저 부분. 저걸 해야 영향받은 row의 개수를 반환해 줌.
아니면 기본값이 getResultList()나 singResult임.
@Test
public void bulkUpdate(){
memberJpaRepository.save(new Member("member1", 10));
memberJpaRepository.save(new Member("member2", 10));
memberJpaRepository.save(new Member("member3", 10));
memberJpaRepository.save(new Member("member4", 10));
memberJpaRepository.save(new Member("member5", 10));
int updatedCount = memberJpaRepository.bulkAgePlus(0);
List<Member> findMembers = memberJpaRepository.findAll();
System.out.println("updatedCount = " + updatedCount);
for (Member findMember : findMembers) {
System.out.println("findMember age = " + findMember.getAge());
}
}
테스트.
다 13됨.
근데 이거 테스트 클래스에 트랜잭션이 달려있는거라서,
저렇게 findAll 해와도 영속성 컨텍스트에 있는건 영속성 컨텍스트에서 먼저 뒤져서 가지고 오는거라,
반영이 안되어 있어서,
public List<Member> findAll(){
em.clear();
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
이렇게 비워놨음.
그냥 잠깐동안 볼려고 저렇게 해 놓은거.
이거 차라리 위에 정의해 놓은 벌크쿼리에,
그러니까
public int bulkAgePlus(){
int updatedCount = em.createQuery("update Member m set m.age= m.age + 3")
.executeUpdate();
em.clear();
return updatedCount;
}
이런 식으로 해 놓는게 훨씬 좋음.
어차피 쿼리 나갈 때는 JPA가 알아서 flush() 해서 (개발자가 직접 작성한 쿼리는 어떤 쿼리일 지 모르니, 일단은 쿼리로 직접 나가는 건 나가기 전에 flush()해서 기존에 쌓아놓은 지연쿼리들 다 DB에 보내고 이 쿼리도 바로 날림. 앞서 얘기한 이유와 마찬가지로 이 쿼리도 바로 날리는 거임.)
나가기 때문에 flush()를 임의로 호출해 줄 필요는 없지만,
영속성은 비워둬야 함.
그래야 select 했을 때 잘 가져옴. 아니면 영속성 컨텍스트에 있는 반영안된 엔티티를 가져올 수도.. (개발자가 어떤 쿼리를 날릴 지 모르니 섣부르게 영속성컨텍스트에 반영할 수는 없음. 비효율적이기도 하고.)
그래서 만약 그냥 저렇게 update 쿼리 날리고 끝낼거면 모르겠는데, 저렇게 벌크 한 다음 조회해와서 뭐 할 계획이면은, 저렇게 해 주는게 좋음.
벌크 쿼리 날리면 DB에는 반영이 되었는데, 영속성 컨텍스트는 반영이 안되어 있으니, 그냥 비워버리고 처음부터 다시 가져오게끔.
여튼 변경되는 건 잘 봤음.
그러면 Spring 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);
이번엔 인자로 받은 age 이상이어야만 바뀌게 해 봤음.
근데 저 @Modifying 저건 뭐냐?
직역 그대로 수정 이라는 뜻 인데, 저걸 하면 아, 개발자가 수정쿼리를 내보냈구나 하고 영향받은 쿼리의 수를 return 시키도록 함. 안 그러면 위처럼 getResultList()나 getSingResult()를 함.
저 clearAutomatically가 뭐냐?
예상한 그대로, 자동으로 clear, 영속성 컨텍스트를 clear 해준다는 거임.
@Test
public void bulkUpdate(){
memberRepository.save(new Member("member1", 9));
memberRepository.save(new Member("member2", 14));
memberRepository.save(new Member("member3", 5));
memberRepository.save(new Member("member4", 26));
memberRepository.save(new Member("member5", 22));
int updatedCount = memberRepository.bulkAgePlus(15);
List<Member> findMembers = memberRepository.findAll();
for (Member findMember : findMembers) {
System.out.println("findMember = " + findMember.getAge());
}
assertThat(updatedCount).isEqualTo(2);
}
15살 이상만 바뀌는 거 확인 함.
clearAutomatically로 자동으로 영속성 컨텍스트가 clear 되어서 새로 가져와 제대로 나오는 것도 확인 함.
벌크 쿼리 날릴 때는,
@Modifying(clearAutomatically = true)
이렇게 붙여줘서 영속성 컨텍스트 clear 해 주기.
그냥 붙여 놓는게 여러모로 좋을 듯. 그러니까 붙여 놓는게 신경 안써도 될 듯.
'스프링데이터 + JPA > 스프링 데이터 JPA' 카테고리의 다른 글
19. JPA 힌트, Lock (0) | 2023.11.23 |
---|---|
18. @EntityGraph (0) | 2023.11.23 |
16. 스프링 데이터 JPA 페이징, 정렬 (0) | 2023.11.21 |
15. 순수 JPA 페이징, 정렬 (0) | 2023.11.21 |
14. 스프링 데이터 JPA 반환 타입 (0) | 2023.11.21 |