Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/meal review] 학식 리뷰 API 기능 개발 #60

Merged
merged 13 commits into from
Nov 18, 2023

Conversation

dldmsql
Copy link
Member

@dldmsql dldmsql commented Nov 11, 2023

작업 내용

학식 리뷰 API 기능 개발을 하는 PR 입니다.

관련 이슈

#56

작업 확인 방법

Swagger를 통해 확인 가능합니다.

추후 작업 예정

  • 학식 응답 형식 변경

현재 ) 날짜 기준으로 아래와 같이 반환

{
  "offeredAt" : "2023-11-11",
  "list": [
   {
   "mealIdx" : 1,
   "menu" : "갈비탕, 밥, 김치",
   "mealType" : "LUNCH",
   "mealStatus" : "OPEN",
   "offeredAt" : "2023-11-11",
   "price" : "10,000.0",
   "category" : "DEFAULT",
   "restaurantName" : "MCC식당"
  },
 ],
}

변경 ) 날짜 + 식당 기준으로 아래와 반환

  • 학식 즐겨찾기 기능 개발
  • 학식 상세 응답 데이터에 리뷰 별점 데이터 추가

궁금한 점

image

case 1 ) 학식 정보 + 리뷰 대표 사진 조회
case 2 ) 학식 정보 + 리뷰 조회 => 이 경우는 데이터가 둘다 존재하지 않음
case 3 ) 학식 정보 + 리뷰 조회

API가 분리되어야 하는지 이 3가지 경우를 아우르는 API로 개발해야 하는지 방향성을 잡지 못했습니다..

커서 기반 페이징으로 우선 구현을 하고 있습니다.!( 무한 스크롤 방식으로 갈 것 같아서 )
이때 조회된 데이터를 VO 값으로 가져오고 싶으나, 리뷰 엔티티와 N 관계를 갖는 이미지 엔티티의 imgUrl만 추출하는 방식을 찾지 못했습니다.
그래서 현재는 리뷰 엔티티를 조회해서 map() 방식으로 처리하고 있습니다.
좋은 아이디어 있을까요..??

select r.idx as reviewIdx, r2.name as restname, m.meal_type as mealtype, r.grade, r.content, i.image_url, count(rm.review_idx) as reviewcnt
from reviews r
left join images i on r.idx = i.review_idx
left join review_marks rm on r.idx = rm.review_idx
left join meal m on r.meal_idx = m.idx
left join restaurant r2 on m.restaurant_idx = r2.idx
where meal_idx = 51
and r.is_deleted = false
and r.idx > 1
group by r.idx, i.idx
order by r.idx desc
limit 8;

위의 쿼리를 querydsl로 ... 옮기고 싶어요 😭

@dldmsql dldmsql added the Feature 기능 개발 label Nov 11, 2023
@dldmsql dldmsql self-assigned this Nov 11, 2023
- 콘솔창에 실행 쿼리의 param이 포함된 채 출력되도록 해줍니다.
- 기존) 날짜로 그룹핑 해서 리스트 형태로 반환
- 변경) 학생식당명을 기준으로 그룹핑해서 MAP 형태로 반환
@dldmsql
Copy link
Member Author

dldmsql commented Nov 16, 2023

추가 작업 내용

  • 학식 조회 쿼리 수정
  • 쿼리 수정에 따른 테스트코드 수정

궁금한 점

제공일자 + 식사구분 ( 아침, 점심, 저녁 )에 해당하는 데이터가 없을 경우

현재 방식 ) 조회 시, 없으면 '등록된 식단이 없습니다.' 로 반환

이렇게 처리를 했었어요.

피그마 상에서 '등록된 식단이 없습니다.'의 데이터에 대해서 리뷰 등록 가능한 페이지가 일단은. 있잖아요.

이 경우, meal idx 가 null 값이다 보니 서버 측에서 해당 데이터로 리뷰를 작성하는 요청을 보내게 된다면 오류로 간주할 수 있어요.

이 경우를 다음 중 어떻게 처리하면 좋을까요?

  1. 클라이언트에서 '등록된 식단이 없습니다.'의 데이터일 경우, 리뷰 작성으로 넘어가지 못하게 막아버리기.

  2. 서버 측에서 식사 데이터 크롤링으로 적재 시, 더미 데이터 형태로 insert 해두기

위의 내용은 메신저로 사전에 공유하였습니다.

  • 추가 질문!

주간 단위 학식 등록 시, saveAll()로 처리하고 있습니다.
saveAll() 사용 시, insert 쿼리가 단건으로 실행됩니다.
이 부분에서 성능 개선을 위해 bulk insert 방식으로 변경해보는 것에 대해서 어떻게 생각하시나요?

참고 블로그 성능 비교

작업 확인 방법

Swagger를 통해 확인 가능합니다.
로컬 환경에서 개발 시, 데이터가 충분하지 않아 DEV 디비 서버 데이터로 테스트하였습니다.
아래는 '명지대학교', '자연캠퍼스', '2023-11-16' 데이터를 기준으로 테스트한 결과입니다.

{
  "localDateTime": "2023-11-16T22:29:11.403944",
  "message": "OK",
  "data": {
    "생활관식당": [
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "BREAKFAST",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "생활관식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "DINNER",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "생활관식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "LUNCH",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "생활관식당",
        "universityName": "명지대학교 자연캠퍼스"
      }
    ],
    "명진당식당": [
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "BREAKFAST",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "명진당식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "DINNER",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "명진당식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "LUNCH",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "명진당식당",
        "universityName": "명지대학교 자연캠퍼스"
      }
    ],
    "학생식당": [
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "BREAKFAST",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "학생식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "DINNER",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "학생식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "LUNCH",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "학생식당",
        "universityName": "명지대학교 자연캠퍼스"
      }
    ],
    "교직원식당": [
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "BREAKFAST",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "교직원식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "DINNER",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "교직원식당",
        "universityName": "명지대학교 자연캠퍼스"
      },
      {
        "mealIdx": 0,
        "offeredAt": "2023-11-16",
        "mealStatus": "OPEN",
        "mealType": "LUNCH",
        "menu": "등록된 식단이 없습니다.",
        "price": 0,
        "category": "DEFAULT",
        "restaurantName": "교직원식당",
        "universityName": "명지대학교 자연캠퍼스"
      }
    ]
  }
}

작업 과정 공유

QueryDsl로 조회 쿼리를 사용했던 것에서 native query 사용으로 방식을 변경했습니다. 이유는 아래와 같습니다.

이유 : 원하는 출력값을 얻기 위해 코드 레벨로 작성하다 보니 쿼리문 작성보다 번거롭고 어렵다.

따라서 JPQL로 방식을 변경하였습니다.

2가지 버전으로 준비했습니다.

  1. EntityManager 사용
  2. JpaRepository 에서 @query 어노테이션 사용

현재 반영된 것은 1번입니다. ( 2번 방식으로의 변경도 가능합니다. )

1번 2번 모두 record 타입의 DTO로 매핑해서 반환합니다.

메신저로 언급드렸던 이슈 상황은 아침, 점심, 저녁 데이터가 존재하지 않는 경우 '등록된 식단이 없습니다.' 로 대체하여 반환하도록 쿼리를 작성했을 때 쿼리 실행은 정상적으로 동작하나 결과 값이 매핑되어 돌아오지 않는 것이었습니다.

제가 직접 쿼리문을 작성했을 때에는 Cross join 예약어 사용이 가능했으나, jpql에서는 이를 지원하지 않습니다. 서브 쿼리로 대체했습니다.
DTO에 매핑되기 위해서는 컬럼 순서, 데이터 타입 등이 모두 일치해야 하는데 순서가 달라 매핑되지 않았습니다.

@Qbeom0925
Copy link
Member

의견
Q. 클라이언트에서 '등록된 식단이 없습니다.'의 데이터일 경우, 리뷰 작성으로 넘어가지 못하게 막아버리기.
이 부분은 저희가 idx 0 내용 NONE으로 넘겨서하는 방식으로 프론트에게 전달하면 될 듯 합니다.
(안드로이드 성식님과 의견을 나누었습니다.)

Q. 벌크 연산 관련
벌크 연산 너무 너무 너무 너무 좋습니다!!!
로직을 보아하니 저장하고 마치기에 문제 없을 것 같습니다!!

Optional<String> campusName) {
// 쿼리 생성
String query =
"SELECT NEW everymeal.server.meal.controller.dto.response.DayMealListGetRes("
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

                """
                     SELECT NEW everymeal.server.meal.controller.dto.response.DayMealListGetRes(
                            COALESCE(m.idx, 0) AS mealIdx,
                            COALESCE(m.offeredAt, :offeredAt) AS offeredAt,
                            COALESCE(m.mealStatus, 'OPEN') AS mealStatus,
                            meal_types.mealType,
                            COALESCE(m.menu, '등록된 식단이 없습니다.') AS menu,
                            COALESCE(m.price, 0) AS price,
                            COALESCE(m.category, 'DEFAULT') AS category,
                            r.name AS restaurantName,
                            CONCAT(u.name, ' ', u.campusName) AS universityName) 
                        FROM Restaurant r 
                        JOIN (SELECT DISTINCT mealType AS mealType FROM Meal) AS meal_types ON 1=1 
                        LEFT JOIN Meal m ON m.restaurant = r AND m.offeredAt = DATE(:offeredAt) AND m.mealType = meal_types.mealType 
                        INNER JOIN University u ON r.university = u 
                        WHERE 
                        u.name = :universityName AND u.campusName = :campusName 
                        ORDER BY r.name ASC """;

요렇게 하면 더 좋을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오!! 바로 수정하겠습니다 : )

Copy link

sonarcloud bot commented Nov 16, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 11 Code Smells

97.4% 97.4% Coverage
0.0% 0.0% Duplication

@Qbeom0925 Qbeom0925 merged commit 52b063d into develop Nov 18, 2023
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature 기능 개발
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants