반응형
Introduction
특정 웹사이트를 보면 검색조건들이 존재한다. 회원검색을 예를 들자면, 회원 목록을 조회할 경우 여러가지 검색 옵션들을 지정해 리스트를 보여준다.
1. 이름 검색
2. 이름 + 나이 검색
3. 이름 + 나이 + 성별 검색
이렇게 여러가지 조건에 따라 그에 부합하는 회원 목록을 보여주기 위해서 해당 회원 조회 쿼리에 조건들을 추가한다.
단순히 Spring Data Jpa 만 사용할 경우에는 인터페이스를 통해 조건에 맞는 함수를 추가적으로 만들어 주어야한다.
하지만 검색조건들이 다양한 만큼 조건에 해당하는 함수를 모두 만드는 일은 쉽지 않다.
이를 QueryDsl 에서 동적으로 검색조건을 동작할 수 있게 지원한다.
조건 검색
1. Spring Data Jpa 인터페이스를 활용한 조건 검색
특정 값의 해당하는 조건의 결과를 얻기 위해 조건에 맞는 함수를 만들어 사용한다.
아래에 예시처럼 이름, 나이, 성별을 검색을 하기위해 각각에 해당하는 함수를 만들어서 사용해야한다.
하지만 이름, 성별을 동시에 검색을 하는 경우의 수도 존재하기 때문에 조건은 세 가지이지만,
그 이상의 함수를 만들어야 하며 조건이 많아질수록 경우의 수는 기하급수적으로 증가한다.
그에 해당하는 모든 함수를 만드는 일은 불필요하고, 비효율적이다.
1-1. Repository
List<Member> findByNameLike(String name);
List<Member> findByNameLikeAndAge(String name,Integer age);
List<Member> findByNameLikeAndAgeAndGender(String name,Integer age, String gender);
1-2. Service
if(name != null) List<Member> findByNameLike(String name);
if(name != null && age != null) List<Member> findByNameLikeAndAge(String name,Integer age);
if(name != null && age != null && gender != null) List<Member> findByNameLikeAndAgeAndGender(String name,Integer age, String gender);
2. QueryDSL를 활용한 동적 조건 검색
QueryDSL은 Query 를 자바 코드로 만들 수 있는 Builder API 이며, 다양한 기능을 제공한다.
제공 되는 기능 중에는 동적 검색에 대해서 편리한 방법을 제공해준다.
(이 게시글에서는 QueryDSL의 기본적인 설치, 사용법이 아닌 QueryDSL 내에서의 동적 조건 검색 방법을 포스팅한다)
1. Querydsl 스크립트에 직접 사용
List<Member> findAllBySearchCondition(MemberSearchCondition condition){
return jpaQueryFactory.
select(member).
from(member).
where(
condition.getName()!=null?member.name.contains(condition.getName()):null,
condition.getAge()!=null?member.age.eq(condition.getAge()):null,
condition.getGender()!=null?member.gender.eq(condition.getGender()):null
).
fetch();
}
직접 사용할 경우 코드가 직관적이기 때문에 가독성이 뛰어나다는 장점이 있다. 하지만 매번 반복적인 코드가 필요하다는 단점이 존재한다.
2. BooleanBuilder 사용
public BooleanBuilder getBuilder(MemberSearchCondition condition){
BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.hasText(condition.getName())){
builder.and(member.name.contains(condition.getName()));
}
if(condition.getAge()!=null){
builder.and(member.age.eq(condition.getAge()));
}
if(condition.getGender()!=null){
builder.and(member.gender.eq(condition.getGender()));
}
return builder;
}
List<Member> findAllBySearchCondition(MemberSearchCondition condition){
BooleanBuilder builder = getBuilder(condition);
return jpaQueryFactory.
select(
member).
from(member).
where(
builder
).
fetch();
}
BooleanBuilder를 사용할 경우 페이지네이션을 위한 카운트 쿼리 및 응답이 다른 함수 등에서 검색 조건의 재활용이 가능하기 때문에 확장성에 용이하다.
3. BooleanExpression 사용
public BooleanExpression integerEq(Integer id, NumberPath<Integer> target) {
return id != null ? target.eq(id) : null;
}
public BooleanExpression stringContains(String txt, StringPath target) {
return StringUtils.hasText(txt) ? target.contains(txt) : null;
}
public Predicate generateWhere(MemberSearchCondition condition) {
return Expressions.asBoolean(true)
.and(dslUtil.integerEq(condition.getAge(), member.age))
.and(dslUtil.stringContains(condition.getName(), member.name))
}
List<Member> findAllBySearchCondition(MemberSearchCondition condition){
return jpaQueryFactory.
select(
member).
from(member).
where(
generateWhere(condition)
).
fetch();
}
QueryDSL의 QType 객체의 필드 타입을 매개변수로 하여 값을 비교하기 때문에 공통 함수로 사용이 가능하며, 검색 조건의 재활용이 가능하기 때문에 확장성에 용이하며, 특정 값의 대한 null 체크 등의 반복적인 코드를 줄여준다.
그 외에도 Qtype 객체를 Path(객체 이름)로 사용할 수 있는 동적 표현식 등의 많은 기술을 지원하기 때문에 꼭 한 번은 QueryDSL 레퍼런스를 읽어보는 것을 추천한다.
반응형
'개발 > QueryDSL' 카테고리의 다른 글
[QueryDSL] 정렬 (동적 정렬) (0) | 2024.03.05 |
---|---|
[QueryDSL] 엔티티 조회 시에 DTO 로 매핑 하는 방법 2탄 (ObjectList in Object) (0) | 2024.03.05 |
[QueryDSL] 동적 업데이트 (0) | 2024.03.04 |
[QueryDSL] 엔티티 조회 시에 DTO 로 매핑 하는 방법 (0) | 2024.02.29 |