반응형
해당 글은 김영한 님의 querydsl을 수강하며 정리하려고 적는 포스팅입니다.
Querydsl 수정 벌크 연산
JPA의 변경 감지는 개별 엔티티 건건이 일어 나는 연산이다
하지만 한 번에 쿼리를 처리해야 하는 경우가 있다면 건건이 날리는 것보다 한 번에 날리고 transaction commit을 하는 것이 성능상 좋다.
쿼리 한번으로 대량 데이터 수정
@Test
@Commit
public void bulkUpdate() {
//member1 = 10 -> member1
//member1 = 20 -> member2
//member1 = 30 -> member3
//member1 = 40 -> member4
long count = queryFactory
.update(member)
.set(member.user, "비회원")
.where(member.age.lt(28))
.excute();
em.flush();
em.clear();
//member1 = 10 -> 비회원
//member1 = 20 -> 비회원
//member1 = 30 -> member3
//member1 = 40 -> member4
List<Member> result = queryFactory
.selectFrom(member)
.fetch();
for (Member member1 : result) {
System.out.println("member1 = " + member1)
}
}
벌크 연산 주의점
JPA에서 member1~4는 영속성 컨텍스트에 올라가 있다.
벌크 연산은 영속성 컨텍스트를 무시하고 DB에 바로 update 한다.
JPA는 DB에서 값을 가지고 왔어도 영속성 컨텍스트에 값이 있으면 영속성 컨텍스트의 값을 사용한다.
영속성 컨텍스트를 우선한다.
flush(), clear() 하지 않은 영속성 컨텍스트
- member1 = Member(id=1, username=member1, age=10)
- member1 = Member(id=2, username=member2, age=20)
- member1 = Member(id=3, username=member3, age=30)
- member1 = Member(id=4, username=member4, age=40)
해결 방법
영속성 컨텍스트를 먼저 flush() 해서 DB로 내보낸다.
그리고 영속성 컨텍스트를 clear()로 초기화한다.
어차피 벌크 연산이 일어나면서 DB와 영속성 컨텍스트는 일치하지 않기 때문에 위의 두 가지를 필수로 선행해 준다.
결과
2023-01-15T13:51:14.761+09:00 DEBUG 24440 --- [ main] org.hibernate.SQL :
/* update
Member member1
set
member1.username = ?1
where
member1.age < ?2 */ update member
set
username=?
where
age<?
2023-01-15T13:51:14.831+09:00 DEBUG 24440 --- [ main] org.hibernate.SQL :
/* select
member1
from
Member member1 */ select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
member1 = Member(id=1, username=비회원, age=10)
member1 = Member(id=2, username=비회원, age=20)
member1 = Member(id=3, username=member3, age=30)
member1 = Member(id=4, username=member4, age=40)
기존 숫자에 1 더하기
@Test
public void bulkAdd() {
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
List<Member> result = queryFactory
.selectFrom(member)
.fetch();
for (Member member1 : result) {
System.out.println("member1 = " + member1);
}
}
------------------------------------------------
2023-01-15T14:05:54.841+09:00 DEBUG 27592 --- [ main] org.hibernate.SQL :
/* select
member1
from
Member member1 */ select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
member1 = Member(id=1, username=member1, age=10)
member1 = Member(id=2, username=member2, age=20)
member1 = Member(id=3, username=member3, age=30)
member1 = Member(id=4, username=member4, age=40)
- 실무에서 사용할 일이 많다.
- 곱하기: multiply(x)
- 이 또한 JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화하는 것이 안전하다. (위에 결과를 보면 아래의 DB 와는 다르게 영속성 컨텍스트는 처음 결과와 같은 것을 확인할 수 있다.)
querydsl 삭제 벌크 연산
쿼리 한 번으로 대량 데이터 삭제
@Test
public void bulkDelete() {
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
}
---------------------------------------
2023-01-15T14:09:04.923+09:00 DEBUG 22300 --- [ main] org.hibernate.SQL :
/* select
member1
from
Member member1 */ select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
member1 = Member(id=1, username=member1, age=10)
- 이 또한 JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다.
참고
테스트를 하는 도 중에, bulk 연산 후에는 flush와 clear로 영속성 컨텍스트를 초기화 하는 것이 안전하다고 하였다.하지만, update와는 다르게 delete 벌크연산은 영속성 컨텍스트를 조회해 보면 member1의 결과 값이 1개만 나오는 것이 확인되었다. 즉, DB의 결과와 같았는데.. 그 이유가 무엇인가 해서 찾아보게 되었다. 아래 내용을 참고하자.
bulkUpdate 후에는 해당 엔티티들의 개수와 id는 변하지 않았기 때문에 DB에서 조회한 id 값들도 같을 것이고 1차 캐시에서 해당 엔티티들도 그대로 있을 테니 1차 캐시에 있는 엔티티들을 불러온 것이고, bulkDelete 후에는 DB에서 조회한 id 들에 한해서만 영속성 컨텍스트 1차 캐시에서 값을 조회해 오는 것이라 DB에서 삭제된 1차 캐시의 해당 엔티티는 불러오지 않았습니다.
반응형
'Java' 카테고리의 다른 글
[JPA] 순수 JPA 리포지토리와 Querydsl (0) | 2023.01.16 |
---|---|
[JPA] SQL function 호출하기 (0) | 2023.01.16 |
[JPA] 동적 쿼리 - BooleanBuilder 사용 (0) | 2023.01.14 |
[JPA] 프로젝션과 결과 반환 - @QueryProjection (0) | 2023.01.13 |
[JPA] 프로젝션과 결과 반환 - DTO 조회 (0) | 2023.01.13 |