그로스로그 앱 제작 중 QueryDsl 을 사용했고, 동적쿼리를 생성해보도록 했다.
우선 그로스로그 앱 관리자 화면을 기준으로 사용자를 검색하는 API 를 생성했고, 검색 기준을 구성한 화면을 토대로 정리했다.
위와 같이 검색 기준을 정의했고, 이제 BooleanExpression 을 사용해서 동적 쿼리를 만들 것이다.
WHERE 절에 들어가는 조건을 동적으로 생성할 때 사용하는 것으로, Predicate 를 확장한 타입이다.
Predicate 타입은 QueryDSL 에서 WHERE 절을 구성하는 표현식인데, 연산자 메서드가 지원이 제한되어있고,
이를 확장한 BooleanExpression은 and, or, not 등 논리 연산을 지원하여 지원하고 체이닝 연산을 활용해서 동적 쿼리를 생성해준다.
BooleanExpression nameEq(String name) {
return name.isBlank() ? null : user.name.eq(name);
}
위와 같이 특정 값이 있을 경우에만 쿼리를 작성해주고, 값이 없으면 null 을 반환하는데, null 은 쿼리를 생성하지 않는다.
단, 모든 조건이 null 이 반환될 경우, findAll() 과 동일하다.
그래서 검색에 사용할 객체를 만들고, 프론트단에서 검색 조건이 없을 경우에는 전체 사용자 리스트를 반환하고,
검색 조건이 있을 경우에 해당 쿼리에 맞는 사용자 리스트를 반환한다.
public class UserSearch {
String name;
UserRole userRole;
State state;
CommunityRole communityRole;
Generation generation;
Set<Team> teams;
}
public List<User> findByWhere(UserSearch search) {
return queryFactory
.select(user)
.from(user)
.where(
nameLike(search.getName()),
userRoleEq(search.getUserRole()),
communityRoleEq(search.getCommunityRole()),
generationEq(search.getGeneration()),
teamEq(search.getTeams()),
stateEq(search.getState()))
.leftJoin(user.userTeam, userTeam).fetchJoin()
.leftJoin(userTeam.team, team).fetchJoin()
.orderBy(user.id.asc())
.distinct()
.fetch();
}
private BooleanExpression nameLike(String name) {
return isBlankString(name) ? null : user.profile.name.like("%"+name+"%");
}
private BooleanExpression userRoleEq(UserRole userRole) {
return Objects.isNull(userRole) ? null : user.userRole.eq(userRole);
}
private BooleanExpression communityRoleEq(CommunityRole communityRole) {
return Objects.isNull(communityRole) ? null : user.communityRole.eq(communityRole);
}
private BooleanExpression generationEq(Generation generation) {
return Objects.isNull(generation) ? null : user.generation.eq(generation);
}
private BooleanExpression teamEq(Set<Team> teams) {
return isBlankSet(teams) ? null : user.userTeam.any().team.in(teams);
}
private BooleanExpression stateEq(State state) {
return Objects.isNull(state) ? null : user.state.eq(state);
}
위와 같이 작성하여 API 하나로 사용자 검색에 대한 모든 처리를 가능하게 해준다.
JPA 에서는 메서드쿼리, JPQL 로 쿼리를 작성하는 경우, 모든 경우에 대해서 메서드를 생성해줘야 하고,
Criteria 를 사용해서 동적쿼리를 작성할 수는 있지만, 쿼리 작성이 메서드 체이닝 방식이 아니라서 코드가 좀 복잡하고 길어지는 단점이 있어서 가독성 측면에서 아쉽다.
Criteria 랑 QueryDSL 둘 다 작성을 해보았는데, 확실히 QueryDSL 이 작성하는 게 더 편한거 같다.