[방방곡곡] 프로젝트 회고

HootJem's avatar
Dec 04, 2024
[방방곡곡] 프로젝트 회고
 
공공 데이터 포털 API 를 사용하여 국내 여행지, 음식점, 축제 등을 소개하는 프로젝트이다.
 
팀원: 성훈님(팀장), 윤혜지(나), 은지님, 성윤님, 세연님 (총 5인)
개발 기간 : 2024.09.04 ~ 2024.09.25
사용 기술 : JAVA, SPRINGBOOT, HTML, JAVASCRIPT, JQUERY
 

1. WF 그리기

본인이 구현한 화면은 백엔드 기능까지 완성하기로 했기 때문에 피그마로 화면 작성부터 하였다.
 
notion image
notion image
notion image
내가 담당하게 된 페이지는 이렇게 메인 페이지, 여행지, 상세 정보 페이지이다. (상세 정보 페이지는 내가 만든 페이지를 활용하여 공동으로 사용하게 되었다.)
페이지 만들며 작성한 게시글 → https://inblog.ai/hj/html-구성을-위한-투쟁-28606
 

2. 기능 구현

2-1. 여행지 리스트 구현

notion image
제일 어려웠던 페이지는 이 페이지 였는데
리스트를 출력하기 위해 최신순, 거리순, 인기순 , 시, 군, 구, 전체 이렇게 선택하는 항목과 그에 따라 처리해야 하는 데이터가 다 달랐기 때문이다.
서비스에 시 클릭, SORTBY 클릭, 군 클릭, 이런식으로 나누어 만들지 않고,
조회를 위해 필요한 매개변수들을 DTO 로 담아 내부에서 동적 쿼리를 사용하여 하나의 서비스로 해결하였다.
해당 포스팅
https://inblog.ai/hj/36963 (리스트 출력)
https://inblog.ai/hj/36965 (여러 매개변수 추가 하여 작동하도록)
 
 

2-2. 상세 페이지 구현

notion image
상세 페이지는 공공데이터로 API 요청을 보내서 출력되게 구성되어 있다.
리턴 형식이 JsonData 라서 DTO 로 파싱하여야 했다.
정말 희안하게 단일 데이터 임에도 body → items → item 내부에 배열로 존재했다.
 
해당 포스팅
 
 
 

2-3. 카카오 지도 API 사용

고난에 빠진 팀원분을 도와주며 공부한 덕분에 내 기능 구현에도 유용하게 사용할 수 있었다. 👍
 

3. 프로젝트 완성

 
이렇게 프로젝트는 잘 마무리 되었다.
 
아쉬운 점은 댓글에 사진 첨부 기능이 없다는 점인데 DB 와 여러가지 추가할 사항이 많아져서 시간상 구현하지 못하고 끝나게 되었다.
notion image
 

배운점!

공공API 를 사용할 때 내가 기대한 것 처럼 데이터가 오지 않았다. 이럴때 데이터의 구조를 확인하고 어떻게 꺼내서 프로젝트에 맞게 활용할 수 있는지 알 수 있었다.
 
여러가지 파라미터가 추가된 검색을 이전에 쇼핑몰 프로젝트를 하며 구현했을때는 이렇게 QueryDSL 을 사용하여 구현했었다. 이번에는 JPQL 을 사용하여 구현할 수 있어서 여러 방법을 경험해서 좋았다.
동적 쿼리를 사용하거나, 타입 안정성을 위해서는 QueryDSL 을 사용하는게 낫다고 하지만 기본적인 쿼리문 공부를 위해서 JPQL을 사용하는 의미가 있었다.
@Override public Page<Tuple> selectProductsByCate(ProductPageRequestDTO pageRequestDTO, Pageable pageable){ String sort = pageRequestDTO.getSort(); String seletedCate = pageRequestDTO.getCateCode(); OrderSpecifier<?> orderSpecifier = null; log.info("here1 : " + sort); if (sort != null && sort.startsWith("prodSold")){ orderSpecifier = qProduct.prodSold.desc(); }else if (sort != null && sort.startsWith("prodLowPrice")) { orderSpecifier = qProduct.prodPrice.asc(); }else if (sort != null && sort.startsWith("prodHighPrice")) { orderSpecifier = qProduct.prodPrice.desc(); }else if (sort != null && sort.startsWith("prodScore")) { orderSpecifier = qProduct.tReviewScore.desc(); }else if (sort != null && sort.startsWith("prodReview")) { orderSpecifier = qProduct.tReviewCount.desc(); }else if (sort != null && sort.startsWith("prodRdate")) { orderSpecifier = qProduct.prodRdate.desc(); }else if (sort != null && sort.startsWith("prodHit")) { orderSpecifier = qProduct.prodHit.desc(); }else if (sort != null && sort.startsWith("prodDiscount")){ orderSpecifier = qProduct.prodDiscount.desc(); }else { orderSpecifier = qProduct.prodSold.desc(); } QueryResults<Tuple> results = jpaQueryFactory .select(qProduct, qProductimg) .from(qProduct) .join(qProductimg) .on(qProduct.prodNo.eq(qProductimg.prodNo)) .where(qProduct.cateCode.like(seletedCate+"%")) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .orderBy(orderSpecifier) .fetchResults(); List<Tuple> content = results.getResults(); log.info(content.toString()); long total = results.getTotal(); log.info("total : {}", total); return new PageImpl<>(content, pageable, total); }
public List<Content> findByContentTypeId(String contentTypeId,String sortBy, Pageable pageable) { StringBuilder queryStr = new StringBuilder("select c from Content c where c.contentTypeId = :contentTypeId"); 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.setFirstResult((int) pageable.getOffset()); // 시작 위치 query.setMaxResults(pageable.getPageSize()); // 한 페이지에 표시할 최대 개수 return query.getResultList(); } 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(); }
 
Share article

[HootJem] 개발 기록 블로그