들어가기 앞서..
QueryDSL을 학습하며, 동적쿼리에 대해 알게되었다. 또한 스터디 5주차때 다른 스터디원의 발표를 보며 동적쿼리 사용법을 보게되었다. 조회에서 findOne, findAll, findList 등등 이러한 조회의 경우를 여러개로 분리하지 않고 하나로 작성해줘서 효율적으로 처리하는 것을 볼 수 있었다. 말로만 들었지만 실제 사용해 보지는 않았어서 이번에 공부를 하며 스터디에 적용해보려한다.
동적쿼리란?
동적 쿼리란 상황에 따라 다른 문법의 SQL을 적용하는 것을 말한다. QueryDSL을 사용하게 된다면 이러한 동적쿼리를 사용하는데 강점이 있다.
예를 들어 상황에 따라 조건문이 생성되어 조회를 한다고 가정해보자. 상황들에 대한 예시는 다음과 같다.
- name이 넘어오면 where name = name
- address가 넘어오면 where address = address
- telephone이 넘어오면 where telephone = telehpone
- 위의 조건들이 여러개가 넘어온다면 where name = name and address = address ..
파라미터가 어떻게 넘어오는지에 따라 조건문을 다르게해서 조회할 수 있다. 기존의 정적쿼리를 사용하게 된다면 해당 경우마다 메소드를 생성해주고, Query를 작성해주었다. 동적쿼리를 사용하면 비슷한 조회 코드마다 하나씩 작성해주지 않아도 해결할 수 있다.
동적쿼리를 사용해서 해결하는 방법
위와같은 경우를 동적 쿼리로 해결하기 위해서는 주로 BooleanBuilder, BooleanExpression, StringUtils를 사용한다.
1. BooleanBuilder
private List<Member> searchMember(String name, Integer telephone) {
BooleanBuilder builder = new BooleanBuilder();
if(name != null) {
builder.and(member.name.eq(name));
}
if(telephone != null) {
builder.and(member.age.eq(telephone));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
코드를 보면 알 수 있듯이, if 문을 활용해서 필요한 부분을 BooleanBuilder 쪽에 추가해주면서 쿼리를 생성해주었다.
이렇게 BooleanBuilder를 사용하는 방식은 where 문을 한눈에 알아보기 힘들다는 단점이 있다. 쉽게 말해, 쿼리 형태를 예측하기 어렵다는 것이다. 위에서의 코드는 비교적 간단하기에 알아보기 쉽지만 복잡해지게 되면 가독성이 떨어지는 코드가 될 것이다.
2. BooleanExpression
QueryDSL을 사용할 때 사용하는 방법으로, QueryDSL이 제공해주는 2가지 기능을 사용해서 코드를 작성해준다.
- where()에 null 이 들어오게 되면 무시한다.
- where()에 , 을 and 조건으로 사용한다.
위와 같이 QueryDSL에서 제공해주는 기능을 통해 위에서의 BooleanBuilder를 사용했던 방식을 BooleanExpression으로 바꿔주면 다음과 같다.
private List<Member> searchMember(String name, Integer telephone) {
return queryFactory
.selectFrom(member)
.where(nameEq(name), telephoneEq(telephone))
.fetch();
}
private BooleanExpression nameEq(String name) {
if (name == null) {
return null;
}
return member.name.eq(name);
}
private BooleanExpression telephoneEq(Integer telephone) {
if (telephone == null) {
return null;
}
return member.telephone.eq(telephone);
}
위와 같이 작성해주게 되면 앞서 본 BoolenBuilder보다 searchMember() 라는 메서드를 보았을 때 쉽게 쿼리를 파악할 수 있다.
3. StringUtils
RequestDTO를 통해 값을 받아오고, 그것을 기반으로 동적 쿼리를 생성한다고 가정해보자. 그렇다면 만약 파라미터에 name이 값이 있다면 사용하고, 없다면 사용하지 않을 텐데, 웹에서 파라미터가 넘어올 때 null 이 아닌 "" 로 넘어오는 경우가 많다. 그렇기에 StringUtils.hasText()를 사용해서 처리해주면 된다. 코드 예시는 아래와 같다.
public List<MemberTeamDto> searchByBuilder(MemberSearchCondition condition) {
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.hasText(condition.getUsername())) {
builder.and(member.username.eq(condition.getTeamName()));
}
if (StringUtils.hasText(condition.getTeamName())) {
builder.and(team.name.eq(condition.getUsername()));
}
return queryFactory
.select(new QMemberTeamDto(
member.id.as("memberId"),
member.username,
team.id.as("teamId"),
team.name.as("teamName")))
.from(member)
.leftJoin(member.team, team)
.where(builder)
.fetch();
}
정적쿼리와 동적쿼리의 차이점은 무엇인가?
간단하게 말하자면, 정적쿼리는 어떠한 상황에서도 쿼리의 형태가 동일한 반면에 동적쿼리는 파라미터나 특정 상황에 따라 달라질 수 있는 쿼리이다.
즉, 정적쿼리는 실행 시점에서 쿼리를 수정할 필요가 없을 때나 고정된 쿼리를 재사용할 때 사용하고, 동적쿼리는 실행시점에 쿼리를 동적으로 생성하거나 수정할 때 사용한다.