완성된 모습.
3가지의 정렬 기준을 갖고 화면을 표시한다.

문제상황.
한번에 다 처리하기 보다 하나씩 차근히 처리하기로 했다.
1. 지역과 연동하여 시군구 출력하기.
페이지가 로드될때 
전국 을 제외한 지역은 db에서 불러와 뿌리고 있다.따라서 지역이 클릭될 때 지역 code 를 전달하는 클릭 이벤트를 생성한다.

클릭 이벤트 내부 코드는 다음과 같다.
선택된 값을 전역변수로 관리 하여 파라미터로 넘길 예정.
(클릭 이벤트 호출 마다 해당 값을 여기 저장시킨다.)
// 이렇게 했어도 좋았을걸..
let currentFilter = {
    areaCode: 'all',  // 기본값은 전국
    sigunguCode: '',  // 기본값은 빈 값
    sortBy: ''        // 기본값은 빈 값
};    let activeSigunguItem = null;
    let currentAreaCode = 'all';  // 기본값을 전국으로 설정
    let currentSigunguCode = '';  // 기본값은 빈 값
    let currentSortBy = '';
     
 async function areaClick(areaCode, areaName, element) {
			  // 선택된 지역 스타일 지정 위해 active 클래스 추가
        if (activeItem) {
            activeItem.classList.remove("active");
        }
        element.classList.add("active");
        $(".place__name2").remove(); // 시군구가 나올 div
        activeItem = element;
        currentAreaCode = areaCode;  // 선택한 areaCode 업데이트
        // area 를 클릭하면 sigungu 는 선택되지 않은 상태이므로 빈문자열로 초기화
				currentSigunguCode = '';
				
        await fetchArea(areaCode, areaName); // 지역클릭시 시군구 찾는 fetch
        
        let contentUrl = `get-info?area=${areaCode}`;
        await fetchContent(contentUrl); // 지역 코드만 매개변수로 받아 내부 컨텐츠 찾기
        // "전국"을 클릭한 경우
        if (areaCode === 'all') {
            $(".place__box1__title").text("# 전국");
        }
    }   async function fetchArea(areaCode, areaName){
        let response = await fetch(`get-sigungu?area=${areaCode}`);
        let responseBody = await response.json();
        $(".place__box1__title").text(`# ${areaName}`); //왼쪽 상단 #서울 에 해당
        //2. 받아온 데이터로 div 만들기
        let sigunguList = responseBody.body;
        let placeName2Div = $("<div>").addClass("place__name2");
        for(sigungu of sigunguList){
            let dom = sigunguItem(sigungu);
            placeName2Div.append(dom);
        }
        $(".place__name1").after(placeName2Div)
        requestAnimationFrame(() => {placeName2Div.css({opacity: 1})});
    }
		function sigunguItem(responseBody){
				return ` <div onclick="sigunguClick('${responseBody.code}','${responseBody.area.code}',this)" class="place__name2__item">${responseBody.name}</div>`;
		}여기 까지 하면 왼쪽 상단의 
#지역 과 지역에 맞는 시군구가 랜더링 된다.2. 클릭시 적절한 내부 컨텐츠 출력하기
시군구나 정렬기준 클릭 시 내부 컨텐츠가 알맞게 랜더링 되어야 한다.
적절한 파라미터를 설정하여 요청을 보내면 되도록 코드를 구성했다.
- 시군구 클릭시 → 지역+시군구 코드
`get-info?area=${areaCode}&sigungu=${code}`- 인기순, 최신순 클릭 시 → 현재 선택하고 있는 기준 모두 포함하여 생성
get-info?area=${currentAreaCode}&sigungu=${currentSigunguCode}&sortBy=${currentSortBy}    async function fetchSigungu(areaCode, code){
        let response = await fetch(`get-info?area=${areaCode}&sigungu=${code}`);
        let responseBody = await response.json();
        let contentList = responseBody.body;
        return contentList;
    }
    async function sortContent(sortBy) {
        currentSortBy = sortBy;  // 클릭된 정렬 기준 업데이트
        console.log(currentSortBy);
        let url = `get-info?area=${currentAreaCode}&sigungu=${currentSigunguCode}&sortBy=${currentSortBy}`;
        fetchContent(url);
    }이렇게 하면 필요한 
내부 컨텐츠의 랜더링 및 비동기 통신은 
fetchContent(url) 이 해결한다.
백 로직을 어떻게 구성해야 좋을까?3. 다양한 파라미터를 매개변수로 받는 서버 코드

기존에는 파라미터를 필수가 아닌 상태로 받았다.
곧 
@requestParam 이 4개..5개가 되어가자 너무 혼잡하여 DTO 를 생성하기로 했다.@GetMapping("/get-info")
public ResponseEntity<?> Infoilter(@ModelAttribute ContentRequest.InfoRequestDTO requestDTO) {
    ContentResponse.infoListDTO infoListDTO= contentService.infoContentListWithArea(requestDTO);
    return ResponseEntity.ok(Resp.ok(infoListDTO));
} - RequestDTO
public class ContentRequest {
    @Data
    public static class InfoRequestDTO {
        private final String contentTypeId= "12";
        private String area ="";
        private String sigungu ="";
        private String sortBy ="";
        private int page= 0;
    }
}이 내용들이 모두 파라미터에 들어있었다.
내가 담당한 페이지는 contentTypeId = “12”; 에 해당하는 요소들만 출력하기 때문에
하드코딩 해 놓았다.
- service 코드
public ContentResponse.infoListDTO infoContentListWithArea(ContentRequest.InfoRequestDTO requestDTO){
 
    List<Content> contentList;
    long count = contentRepository.countByContentTypeIdWithOption(requestDTO.getContentTypeId(), requestDTO.getArea(), requestDTO.getSigungu());
    Pageable pageable = PageRequest.of(requestDTO.getPage(), 16);
    if(requestDTO.getArea().equals("all")){
        contentList = contentRepository.findByContentTypeId(requestDTO.getContentTypeId(), pageable);
    }else {
        contentList = contentRepository.findByContentTypeIdAndOption(requestDTO.getContentTypeId(), requestDTO.getArea(), requestDTO.getSigungu(), requestDTO.getSortBy(), pageable);
    }
    List<Area> areaList = new ArrayList<>();
    return new ContentResponse.infoListDTO(contentList, count,areaList);
}메서드 이름이 infoContentListWithArea 라는 의미는 
infoContentList 가  이미 존재한다는 것이다.그렇다… 리팩토링을 한다면 합쳐야 된다고 생각하는 코드.
일단 진행한다.
countByContentTypeIdWithOption
찾은 컨텐츠가 몇 건인지 알기 위해 작성한 jqpl 이다.
StringBuilder jpql = new StringBuilder("select count(*) from Content where contentTypeId = :contentTypeId");StringBuilder 를 사용하여 파라미터에 따라 문자열을 추가 할 수 있게 하였다.
contentTypeId는 필수로 들어갈 것이기 때문에 쿼리문 생성할 때 함께 넣어놓았다.
전체코드는 아래와 같다. 매개변수를 확인할 때
전체인 
all 이 아니면 매개변수가 있거나, DTO 에서 빈문자열로 초기화 했기 때문에
isEmpty 면 충분하다.public long countByContentTypeIdWithOption(String contentTypeId, String areaCode, String sigungucode) {
      StringBuilder jpql = new StringBuilder("select count(*) from Content where contentTypeId = :contentTypeId");
      //지역 코드가 비어있지 않고 all(=전체)가 아닐때 
      if (!areaCode.isEmpty() &&!"all".equals(areaCode)) { 
          jpql.append(" and areaCode = :areaCode");
      }
      // 시군구 코드가 비어있지 않을때
      if(!sigungucode.isEmpty()) {
          jpql.append(" and sigunguCode = :sigungucode");
      }
      Query query = em.createQuery(jpql.toString());
      query.setParameter("contentTypeId", contentTypeId);
      if (!areaCode.isEmpty() && !"all".equals(areaCode)) {
          query.setParameter("areaCode", areaCode);
      }
      if (!sigungucode.isEmpty()) {
          query.setParameter("sigungucode", sigungucode);
      }
      return (Long) query.getSingleResult();
  }이걸 동적 쿼리라고 한다.
findByContentTypeId동적쿼리로 나누면 될텐데 굳이 서비스에서 분기를 나눈 이유는
여러가지 쿼리 작성을 위해서 이다.
List<Content> contentList;
if(requestDTO.getArea().equals("all")){
    contentList = contentRepository.findByContentTypeId(requestDTO.getContentTypeId(), pageable);
}else {
    contentList = contentRepository.findByContentTypeIdAndOption(requestDTO.getContentTypeId(), requestDTO.getArea(), requestDTO.getSigungu(), requestDTO.getSortBy(), pageable);
}일단 얘 역시 여러가지 파라미터를 동적으로 할당받고 있기 때문에 동적쿼리 작성이 필요하다.
public List<Content> findByContentTypeIdAndOption(String contentTypeId, String area, String sigunguCode,String sortBy, Pageable pageable) {
      StringBuilder queryStr = new StringBuilder("select c from Content c where c.contentTypeId = :contentTypeId and c.areaCode= :area");
      if (sigunguCode != null && !sigunguCode.isEmpty()) {
          queryStr.append(" and c.sigunguCode = :sigunguCode");
      }
      if (sortBy != null && !sortBy.isEmpty()) {
          if (sortBy.equals("createdTime")) {
              queryStr.append(" order by c.createdTime desc");
          } else if (sortBy.equals("viewCount")) {
              queryStr.append(" order by c.viewCount desc");
          }
      }
      Query query = em.createQuery(queryStr.toString(), Content.class);
      query.setParameter("contentTypeId", contentTypeId);
      query.setParameter("area", area);
      if (sigunguCode != null && !sigunguCode.isEmpty()) {
          query.setParameter("sigunguCode", sigunguCode);
      }
      query.setFirstResult((int) pageable.getOffset());  // 시작 위치
      query.setMaxResults(pageable.getPageSize());       // 한 페이지에 표시할 최대 개수
      return query.getResultList();
  }정렬 기준이 있거나 없을 수 있어서 먼저 확인을 해 주었다.
여기까지 하면 이런 데이터가 리턴된다. (너무 길어서 몇 개 삭제함)
ContentResponse.infoListDTO(
    count = 877,
    contents = [
        ContentResponse.infoListDTO.ContentDTO(
            title = "가계해수욕장",
            contentId = 126273,
            addr1 = "전라남도 진도군 고군면 신비의바닷길 47",
            areaCode = 38,
            contentTypeId = 12,
            firstImage = "http://tong.visitkorea.or.kr/cms/resource/36/3079736_image2_1.jpg"
        ),
        ContentResponse.infoListDTO.ContentDTO(
            title = "거금생태숲",
            contentId = 2032450,
            addr1 = "전라남도 고흥군 금산면 거금일주로 1877",
            areaCode = 38,
            contentTypeId = 12,
            firstImage = "http://tong.visitkorea.or.kr/cms/resource/80/3347880_image2_1.JPG"
        )
    ],
    areas = [
        ContentResponse.infoListDTO.AreaDTO(code = 1, name = "서울"),
        ContentResponse.infoListDTO.AreaDTO(code = 2, name = "인천"),
        ContentResponse.infoListDTO.AreaDTO(code = 38, name = "전라"),
        ContentResponse.infoListDTO.AreaDTO(code = 39, name = "제주")
    ]
)
프론트에서 count, contents, areas 안에 있는 애들을 꺼내 쓰면 끝
Share article



