여행지 검색 기능 개발#3

Park JeaHyun·2022년 9월 13일
0

DAMDA

목록 보기
5/6

기본적인 엔티티 설계와 개발을 완료한 후 본격적인 검색 기능 개발을 진행했다. 검색 조건은 크게 2가지가 있다.

  1. 키워드로 검색
  • 검색하고자 하는 키워드를 검색창에 입력해서 여행지를 찾을 수 있다.
  1. 태그 조건으로 검색
  • 여행지에 작성된 리뷰의 태그들을 기준으로 여행지를 찾을 수 있다.

키워드로 검색

키워드 검색을 위해서 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의 낮은 학습 수준으로 인해서 다른 방식으로 태그 조건들을 필터링하는 방식을 구현했다.

  1. 일단 모든 여행지들을 불러온다. -> List<Spot>
  2. 2중 반복문을 통해서 여행지들을 돌면서 태그 조건들이 포함되어 있는지 확인한다.
    2-1. Spot -> SpotDto 로 변경한다.
    2-2. 여행지에 원하는 태그가 포함되어 있다면 태그 카운트 필드를 +1
  3. 갱신된 태그 카운트를 기준으로 리스트를 정렬한다.
  4. 리스트를 Page로 변환한다.
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;
    }

0개의 댓글