여행계획 사이트인 <원더웨이>를 개발하며 작성했던 동적 쿼리에 대해 포스팅해보려고 한다.
원더웨이의 핵심기능중 하나는 다른 사용자들의 계획을 참고할 수 있는 기능인데, 검색창의 편의성이 굉장히 중요했다.
그래서 도입했던 방식이, input-box 오른쪽에 카테고리를 선택하는 select-box를 두고, 각 카테고리와 검색어를 매칭해 저장한 뒤, 한번에 검색하는 방식이다.
즉, 위와같이 select-box에서 Location을 선택한 뒤, "제주도"를 입력해 엔터를 누르면 검색 기준이 하나 생긴다.
또한 select-box에서 Period를 선택한 뒤, "3"을 입력하고 엔터를 누르면 또다른 검색기준이 생긴다.
그리고 이때 Search버튼으로 검색을 요청하면, 제주도에서 3일간 여행하는 계획이 검색되는것!
그래서 단순 JPA, 특히 JPQL과 Spring-Data-JPA로 백엔드를 구현하던 나는, 위 조건을 유연하게 처리하는 로직을 어떻게 세울 수 있을까? 라는 고민을 하게 되었고, 그 결론으로 나온것이 '동적 쿼리'였다! 각기 다른 요청마다 쿼리가 동적으로 변환되어 적절한 결과를 리턴할 수 있도록 하는것이다.
어떻게?
동적쿼리의 작성은 Criteria, Named Query, 심지어 JPQL도 가능하지만 QueryDSL이 가독성이 좋아 선택하게 되었다.
우선 구분조건으로 필요한건 '태그'와 '기간'이라고 설정하고, 해당 데이터 기준으로 동적으로 SQL을 생성하도록 하겠다.
1. 쿼리 조건을 누적시킬 BooleanBuilder 생성
BooleanBuilder builder = new BooleanBuilder();
and, or등으로 조건을 추가할 수 있다.
2. 각 경우에 대해 조건 추가
//태그 기준으로 검색
if(searchDto.getTags() != null && !searchDto.getTags().isEmpty()){
BooleanExpression tagExpression = qplan.tags.any().tag.text.in(searchDto.getTags());
builder.and(tagExpression);
}
요청객체에 태그정보가 있는지 확인후, 있다면 tag관련 조건식을 추가한다. QPlan을 통해 엔티티필드에 접근하고, 기조 ㄴ데이터중 현재 검색어에 맞는 데이터 리스트를 필터링 할 수 있도록 in()을 사용하는 것이다. 이를 빌더에 누적해준다.
//기간 기준으로 검색
if(searchDto.getPeriods() != null && !searchDto.getPeriods().isEmpty()){
BooleanExpression periodExpression = qplan.period.in(searchDto.getPeriods());
builder.and(periodExpression);
}
기간 조건도 마찬가지로 요청객체에 정보가 있는지 확인 후, in()을 활용해 조건식을 검색한다.
3. JPAQuery로 쿼리 실행
JPAQuery<Plan> query = new JPAQuery<>(entityManager);
Long total = query.from(qplan)
.where(builder)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
QueryDSL이 가독성 좋은 동적쿼리를 지원한다는것을 한눈에 알 수 있는 대목이다. from(), where(), offset(), limit() 등 메소드를 통해 쿼리를 조립한다. 특히 offset과 limit을 통해 편리하게 페이징이 가능했다.
검색결과엔 현재 페이지 말고도 총 검색결과에 대한 카운트도 필요하므로, 다음과같이 레코드 수를 카운트 하는 쿼리도 만들어줬다.
Long total = query.from(qplan)
.where(builder)
.select(qplan.id.countDistinct())
.fetchOne();
long totalCount = (total != null) ? total : 0;
이렇게 완성된 결과인 content와 totalCount를 적절히 응답에 담아 리턴해주면 된다.
이와같은 방법으로, QueryDSL을 활용해 다양한 검색조건을 동적으로 추가하고, 결과를 조회 할 수 있게 되었다. 동적쿼리를 통해 다양한 검색조건을 자동으로 조합해 유연한 검색결과를 줄 수 있었고, QueryDSL덕에 가독성은 물론 직관적인 쿼리를 작성할 수 있었다. 개발을 하다보면 이러한 SQL쿼리를 작성할 일이 많은데, 동적쿼리를 통한 효율적인 개발을 체화 할 수 있었다😎 전체 코드는 이곳에서 볼수있다!
EnjoyTrip/src/main/java/com/ssafy/wanderway/repository/SearchRepository.java at main · Wander-Way/EnjoyTrip
Ssafy 관통 프로젝트[SSAFY 관통 PJT 우수상]. Contribute to Wander-Way/EnjoyTrip development by creating an account on GitHub.
github.com
'프로젝트 > 원더웨이' 카테고리의 다른 글
Vue3.js에서 TMap API 사용하는 방법(바닐라JS를 Vue로 커스텀) (13) | 2024.11.11 |
---|---|
정규표현식&프롬프트 엔지니어링으로 생성형 AI응답을 객체화 하기 (9) | 2024.11.09 |