
이번 글에서는 Querydsl을 이용해 동적 쿼리를 작성하는 방법에 대해 알아보겠습니다.
기본적으로 2가지의 방법이 있는데요, BooleanBuilder 객체를 이용하는 방법과 WHERE 절에 다중 파라미터를 넣는 방법이 있습니다. BooleanBuilder 객체를 이용하는 방법을 먼저 알아보겠습니다.
이름과 나이를 조건으로 해당하는 회원을 찾는 쿼리를 예시로 사용할텐데요, 굉장히 간단해서 코드로 먼저 보겠습니다.
// BooleanBuilder를 사용한 동적 쿼리
@Test
void dynamicQuery_BooleanBuilder() {
String usernameParam = "memberA";
Integer ageParam = 10;
List<Member> members = searchMemberA(usernameParam, ageParam);
assertThat(members.size()).isEqualTo(1);
}
이름이 memberA이고, 나이가 10살인 회원은 1명 밖에 없습니다. searchMember 매서드를 살펴보겠습니다. BooleanBuilder 객체는 이곳에서 사용됩니다.
private List<Member> searchMember(String usernameParam, Integer ageParam) {
// BooleanBuilder 객체를 생성한다.
BooleanBuilder builder = new BooleanBuilder();
// 파라미터 null값 여부를 검사한다.
if (usernameParam != null) {
builder.and(member.username.eq(usernameParam));
}
if (ageParam != null) {
builder.and(member.age.eq(ageParam));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
보시다시피 BooleanBuilder 객체를 생성해 .and() 매서드를 통해 조건을 명시합니다. 그리고 조립된 Builder를 WHERE() 절의 조건에 넣어주면 끝입니다.
만약 매서드에 null값이 넘어와도 아무런 문제가 생기지 않습니다. 코드에서 null 체크를 하기 때문에 만약 파라미터에 넘어온 값이 null이라면 쿼리에 해당 파라미터가 조건에서 제외된 채 나갑니다. 또한 파라미터에 넘어오는 값이 null이 아님을 확신한다면, BooleanBuilder 객체 생성 시에 파라미터를 넘겨주는 방법도 있습니다.
private List<Member> searchMember(String usernameParam, Integer ageParam) {
// 파라미터와 함께 BooleanBuilder 객체를 생성한다.
BooleanBuilder builder =
new BooleanBuilder(member.username.eq(usernameParam));
// 파라미터 값 여부를 검사한다.
if (ageParam != null) {
builder.and(member.age.eq(ageParam));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
그리고 WHERE 다중 파라미터 방식을 알아보겠습니다. 이 방식도 매우 간단해 코드로 먼저 보겠습니다.
// 다중 WHERE 파라미터 사용
@Test
void dynamicQuery_WHEREParam() {
String usernameParam = "memberA";
Integer ageParam = null;
List<Member> members = searchMember2(usernameParam, ageParam);
assertThat(members.size()).isEqualTo(1);
}
위와 같은 파라미터와 테스트입니다. searchMember2 매서드를 살펴보겠습니다.
private List<Member> searchMember2(String usernameParam, Integer ageParam) {
return queryFactory
.selectFrom(member)
.where(usernameEq(usernameParam), ageEq(ageParam))
.fetch();
}
보시면 null을 체크하는 부분 없이 바로 쿼리가 만들어지는 것을 볼 수 있습니다. 보면 WHERE 절에 두 가지 매서드를 사용하고 있는데요, 이 매서드들을 보겠습니다.
private BooleanExpression usernameEq(String usernameParam) {
if (usernameParam == null) {
return null;
}
return member.username.eq(usernameParam);
}
private BooleanExpression ageEq(Integer ageParam) {
if (ageParam == null) {
return null;
}
return member.age.eq(ageParam);
}
보시다시피 파라미터를 매칭하는 부분을 매서드로 빼서 사용하고 있습니다. 나아가 위 코드를 삼항연산자로 바꿔보겠습니다.
private BooleanExpression usernameEq(String usernameParam) {
return usernameParam != null ? member.username.eq(usernameParam) : null;
}
private BooleanExpression ageEq(Integer ageParam) {
return ageParam != null ? member.age.eq(ageParam) : null;
}
결과적으로 전체적인 가독성이 매우 좋아진 것을 확인할 수 있습니다. 그리고 이 방식을 더 확장시킬 수 있습니다. 현재 매서드의 파라미터는 2개이고 이걸 각각 매서드로 따로 빼놓은 상태인데요, 이 빼놓은 매서드를 allEq() 라는 이름으로 다시 하나의 매서드에 담을 수 있습니다. 코드로 보겠습니다.
private List<Member> searchMember2(String usernameParam, Integer ageParam) {
return queryFactory
.selectFrom(member)
.where(allEq(usernameParam, ageParam))
.fetch();
}
private BooleanExpression allEq(String usernameParam, Integer ageParam) {
return usernameEq(usernameParam).and(ageEq(ageParam));
}
이 방식에도 파라미터 갯수마다 매서드를 만들어줘야 한다는 굉장히 큰 단점이 있긴 합니다. 또한 allEq() 매서드 방식처럼 모든 파라미터를 일괄적으로 받으려면 사실 allEq() 매서드 내부에 null값을 체크하는 부분이 있기도 해야 합니다. 하지만 이렇게 코드를 분리해놓으면 재사용성 측면에서 좋을 뿐만 아니라, 특정 서비스에 대해서는 일괄적으로 조건을 묶어 한 번에 사용할 수 있기 때문에 개발/관리하는 측면에서 더욱 효율적일 경우도 있습니다. 그래서 김영한님도 실무에서 이 방법을 자주 사용한다고 합니다.
다음 글에서는 예제를 통해 이번 글에서 배운 것들의 실제 사용법을 알아보겠습니다.
정말 유익한 글이었습니다.