동적쿼리
where 다중 파라미터 사용
@Test
public void dynamicQuery_WhereParam(){
String usernameParam = "member1";
Integer ageParam = 10;
// 동적 쿼리로 이름이 member1이고 나이가 10살인 사람을 찾고 싶은 상황
List<Member> result = searchMember2(usernameParam,ageParam);
Assertions.assertThat(result.size()).isEqualTo(1);
}
동적 쿼리로 들어오는 이름과 나이 변수에 맞춰 다른 값을 리턴시켜보자.
private List<Member> searchMember2(String usernameCond, Integer ageCond){
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
return queryFactory.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
where 절에 다중 파라미터를 사용하는 방식은 다음과 같이 아주 간단하게 코드를 작성할 수 있다.
물론 usernameEq, ageEq 같은 메소드를 만들어야 한다는 단점이 있긴 하다.
private Predicate ageEq(Integer ageCond) {
return ageCond == null ? null : member.age.eq(ageCond);
}
private Predicate usernameEq(String usernameCond) {
if (usernameCond != null) {
return member.username.eq(usernameCond);
}
else{
return null;
}
}
메소드는 위와 같다 여기서 Predicate는 querydsl의 조건식을 나타내는 인터페이스이다.
따라서 ageCond가 들어왔을 때 null이라면 null을 바로 반환하고, null이 아니라면 조건식을 반환한다.
.where(usernameEq(usernameCond), ageEq(ageCond))
반환된 조건식은 이 where 절에 들어가게 되는데,
만약 반환값이 null이라서 where 절에 null이 들어갔다면 자동으로 무시가 된다.
그 덕분에 동적 쿼리를 만들 수 있는 것이다.
age에 null을 넣고 쿼리를 실행시켜 보자
다음과 같이 where 절에 조건이 들어가지 않은 것을 확인할 수 있다.
조건 합친 메소드 만들기
private BooleanExpression allEq(String usernameCond, Integer ageCond){
return usernameEq(usernameCond).and(ageEq(ageCond));
}
이렇게 각각의 메소드를 합쳐 하나의 메소드로도 만들 수 있다.
여기선 반환형이 BooleanExpression으로 바뀌었는데
이 참에 Predicate와 BooleanExpression의 차이점을 알아보자.
Predicate는 인터페이스로서 Predicate를 상속받은 여러 조건식을 모두 아우를 수 있는 반환형이다.
유연성이 높다는 장점이 있다.
BooleanExpression은 Predicate의 하위 타입으로 논리적인(참. 거짓)조건을 표현하는데 특화된 클래스 이다.
BooleanExpression을 반환형으로 사용할 때는 해당 메서드가 오직 논리 조건을 반환한다는 것을 명확하게 표현할 수 있다.
또한 BooleanExpression은 같은 클래스끼리 and() 나 or() 연산이 가능하기에,
추가 적인 논리 연산이 필요한 경우 클래스를 맞춰 주어야 한다.
(물론 Predicate는 인터페이스기 때문에 and나 or 메소드를 사용할 수 없다.)
따라서 위의 상황에서도 and 연산을 사용해야하기 때문에 BooleanExpression으로 반환형을 바꿔준 것이다.
위와 같이 메소드를 합치는 방법은 조건이 많아지면 많아질 수록 활용성이 높아진다.
정리
- where 절에 null 조건은 무시 되기에 동적 쿼리를 만들 수 있다.
- 한 번 만들어놓은 메소드는 다른 쿼리에서도 재활용이 가능하다.
- 쿼리 자체의 가독성이 높아진다.
벌크 연산
수정, 삭제, 배치 쿼리
기존 JPQL은 더티 체킹이라 하여,
엔티티에 변경이 일어나면 그를 감지하여 update문을 날린다.
하지만 이 더티 체킹은 엔티티 변경이 일어나는 매 건마다 update 문을 날리기 때문에
기능상 좋지 못 하다. 이를 극복하기 위한 벌크연산을 배워보자.
@Test
@Commit
public void bulkUpdate(){
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
// update 쿼리는 결과로 영향을 받은 행 수가 나온다.
// 현재 나이가 20살 이하인 행은
// member1 하나만 존재한다.
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.loe(20))
.execute();
}
위와 같은 update 쿼리를 날리면 끝이다.
실행 결과도 잘 나온 것을 확인할 수 있다.
주의할 점
벌크 연산은 항상 조심해야할 것이 있다.
JPA는 항상 영속성 컨텍스트에 엔티티를 올려둔다.
현재 모든 멤버가 영속성 컨텍스트에 올라가 있는 상태다.
member 1 - > 10
member 2 - > 20
member 3 - > 30
member 4 - > 40
여기서 벌크연산은 영속성 컨텍스트를 무시한 채 DB에 바로 쿼리를 날린다.
따라서 DB의 상태와 영속성 컨텍스트의 상태가 달라진다.
이 상태에서 queryfactory.selectFrom(member).fetch;
로 값을 가져오면 영속성 컨텍스트에 있는 값이 반환된다.
따라서 벌크 연산 이후엔 em.flush, em.clear()를 해줘서 초기화를 시켜야 한다.
영속성 컨텍스트는 트랜잭션 단위로 유지되기 때문에
서비스단에서 한 트랜잭션 내부에 벌크 연산을 만들어 뒀다면 초기화 시킬 필요가 없긴 하다.
전체 값을 더하거나 빼고 싶을 경우
@Test
public void bulkAdd(){
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
}
위와 같이 add를 사용하면 된다. minus는 따로 존재하지 않기 때문에 -1을 add 해주는 식으로 진행해야한다.
곱하기는 .multifly를 사용하면 된다.
삭제 연산
@Test
public void bulkDelete(){
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
long count = queryFactory
.delete(member)
.where(member.age.eq(10))
.execute();
}
삭제는 위와 같이 delete를 이용하여 진행해주면 된다.
참고: 김영한 실전! Querydsl
'Tools & Libraries > Querydsl' 카테고리의 다른 글
[Querydsl] 순수 JPA와 Querydsl 1. (순수 JPA 리포지토리에서 querydsl 사용해보기) (0) | 2024.10.12 |
---|---|
[Querydsl] Querydsl 중급문법 4 (SQL function 호출하는 방법) (1) | 2024.09.13 |
[Querydsl] Querydsl 중급문법 2 (@QueryProjection, 동적쿼리 - BooleanBuilder) (0) | 2024.09.11 |
[Querydsl] Querydsl 중급문법 1 (프로젝션과 결과 반환 - Dto) (0) | 2024.09.11 |
[Querydsl] Querydsl 기본문법 5 (Case 문, 상수 문자 더하기) (0) | 2024.09.06 |