기본적인 엔티티 설계와 개발을 완료한 후 본격적인 검색 기능 개발을 진행했다. 검색 조건은 크게 2가지가 있다.
키워드 검색을 위해서 JPA Specification을 사용했다.
키워드 검색에 활용할 데이터 목록
검색 조건을 반환하는 메서드
JpaRepository에서 사용할 수 있는 검색 조건(where문)을 반환하는 search 메서드를 만들었다.
private Specification<Spot> search(String kw) {
return new Specification<>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<Spot> s, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true); // 중복을 제거
Join<Spot, Review> r = s.join("reviews", JoinType.LEFT);
Predicate predicateSearchCondition = cb.or(cb.like(s.get("name"), "%" + kw + "%"),
cb.like(s.get("city"), "%" + kw + "%"),
cb.like(s.get("address"), "%" + kw + "%"),
cb.like(r.get("content"), "%" + kw + "%"));
Predicate predicateSelfMadeFlag = cb.equal(s.get("selfMadeFlag"), "Y");
return cb.and(predicateSelfMadeFlag, predicateSearchCondition);
}
};
}
위 검색 조건으로 검색된 여행지를 정렬하는 기준은 리뷰 갯수로 정렬하기로 결정했다. 또한 컨트롤러에서 넘어오는 데이터는 검색어와 태그 검색 조건이 담긴 리스트이다.
public Page<SpotDto> getSpotListBy(String searchWord, List<String> checkedValue, int page) {
Page<SpotDto> spotDtoPages;
Specification<Spot> spec;
... 생략 ...
else if (!searchWord.equals("") && checkedValue.size() == 0) {
System.out.println("검색어만 있음");
spec = search(searchWord);
Pageable pageable = getPageRequest(page, 8, "reviewCnt");
Page<Spot> spotPages = spotRepository.findAll(spec, pageable);
spotDtoPages = new SpotDto().toDtoList(spotPages);
}
... 생략 ...
return spotDtoPages;
}
public Pageable getPageRequest(int page, int pageSize, String condition) {
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc(condition));
return PageRequest.of(page, pageSize, Sort.by(sorts));
}
여행지에 작성된 리뷰에는 태그도 같이 작성되도록 했다.
컨트롤러에서 넘어온 태그들이 적용된 여행지를 가져오기 위해선 복잡한 쿼리문 작성(by JPQL)을 통해서도 가능할 것이다. 하지만 현재 JPA의 낮은 학습 수준으로 인해서 다른 방식으로 태그 조건들을 필터링하는 방식을 구현했다.
List<Spot>
public Page<SpotDto> filterAndSortByTag(int page, List<Spot> spotList, List<String> checkedValue) {
List<SpotDto> spotDtoList = new ArrayList<>(); // 필터된 여행지 리스트
// 태그 카운트 및 필터
for (Spot spot : spotList) {
Map<Tag, Integer> tagInfo = spot.getTagMap();
SpotDto spotDto = new SpotDto().toDto(spot);
for (Tag tag : tagInfo.keySet()) { // 여행지 태그들
if (checkedValue.contains(tag.getName())) { // 여행지 태그가 검색 조건에 포함
if (!spotDtoList.contains(spotDto)) {
spotDtoList.add(spotDto);
}
spotDto.setTagCnt(spotDto.getTagCnt() + tagInfo.get(tag));
}
}
}
// 태그 카운트를 기준으로 정렬
Comparator<SpotDto> comparator = new Comparator<SpotDto>() {
@Override
public int compare(SpotDto a, SpotDto b) {
return b.getTagCnt() - a.getTagCnt();
}
};
Collections.sort(spotDtoList, comparator);
Pageable pageable = PageRequest.of(page, 8);
final int start = (int)pageable.getOffset();
final int end = Math.min((start + pageable.getPageSize()), spotDtoList.size());
Page<SpotDto> spotDtoPages = new PageImpl<>(spotDtoList.subList(start, end), pageable, spotDtoList.size());
return spotDtoPages;
}
검색어와 태그 검색 조건 둘 다 포함되어 있는 경우는 일단 검색어로 여행지를 검색하고 이후에 위 메서드를 통해서 필터링하는 방식을 사용했다.
public Page<SpotDto> getSpotListBy(String searchWord, List<String> checkedValue, int page) {
Page<SpotDto> spotDtoPages;
Specification<Spot> spec;
... 생략 ...
else if (searchWord.equals("") && checkedValue.size() != 0) {
System.out.println("검색 조건만 있음");
List<Spot> spotList = spotRepository.findAllEntityGraph();
spotDtoPages = filterAndSortByTag(page, spotList, checkedValue);
} else {
System.out.println("둘다 있음");
spec = search(searchWord);
List<Spot> spotList = spotRepository.findAll(spec);
spotDtoPages = filterAndSortByTag(page, spotList, checkedValue);
}
return spotDtoPages;
}