From 78f64ce65cf055599b44d65ec396e196e8d7c311 Mon Sep 17 00:00:00 2001 From: LeeKW Date: Mon, 29 Sep 2025 11:29:08 +0900 Subject: [PATCH 1/4] fix : bugs of testCase, init data --- src/main/java/com/back/global/init/DevInitData.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/back/global/init/DevInitData.java b/src/main/java/com/back/global/init/DevInitData.java index a36d2a5..48233c5 100644 --- a/src/main/java/com/back/global/init/DevInitData.java +++ b/src/main/java/com/back/global/init/DevInitData.java @@ -58,6 +58,18 @@ ApplicationRunner devInitDataApplicationRunner() { }; } + @Transactional + public void cocktailInit() throws Exception { + // H2 DB에 이미 데이터가 들어가 있는지 확인 + if (cocktailRepository.count() > 0) { + System.out.println("Cocktail 데이터가 이미 존재합니다."); + return; + } + + // data-h2.sql에서 자동 삽입되므로 여기서는 추가하지 않음. + System.out.println("Cocktail 초기화: CSV에서 데이터를 이미 로드합니다."); + } + @Transactional public void userInit() { userRepository.findByNickname("사용자A").orElseGet(() -> From c2719d7a398022504bc55e6c64fab964b19c3024 Mon Sep 17 00:00:00 2001 From: LeeKW Date: Mon, 29 Sep 2025 11:47:34 +0900 Subject: [PATCH 2/4] fix : bug --- src/main/java/com/back/global/init/DevInitData.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/com/back/global/init/DevInitData.java b/src/main/java/com/back/global/init/DevInitData.java index 48233c5..a36d2a5 100644 --- a/src/main/java/com/back/global/init/DevInitData.java +++ b/src/main/java/com/back/global/init/DevInitData.java @@ -58,18 +58,6 @@ ApplicationRunner devInitDataApplicationRunner() { }; } - @Transactional - public void cocktailInit() throws Exception { - // H2 DB에 이미 데이터가 들어가 있는지 확인 - if (cocktailRepository.count() > 0) { - System.out.println("Cocktail 데이터가 이미 존재합니다."); - return; - } - - // data-h2.sql에서 자동 삽입되므로 여기서는 추가하지 않음. - System.out.println("Cocktail 초기화: CSV에서 데이터를 이미 로드합니다."); - } - @Transactional public void userInit() { userRepository.findByNickname("사용자A").orElseGet(() -> From 470273d05cb2645420efea5bf83b0ddc3211ee15 Mon Sep 17 00:00:00 2001 From: LeeKW Date: Wed, 8 Oct 2025 14:17:03 +0900 Subject: [PATCH 3/4] refactor : add cocktailPreview field in cocktailSearchResponseDto --- .../dto/CocktailSearchResponseDto.java | 54 +++++++++---------- .../cocktail/service/CocktailService.java | 15 +----- 2 files changed, 28 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java index 06078a1..63e10c8 100644 --- a/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java +++ b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java @@ -1,34 +1,34 @@ package com.back.domain.cocktail.dto; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import com.back.domain.cocktail.entity.Cocktail; -@Getter -@Setter -@NoArgsConstructor -public class CocktailSearchResponseDto { +public record CocktailSearchResponseDto ( - private long cocktailId; - private String cocktailName; - private String cocktailNameKo; - private String alcoholStrength; - private String cocktailType; - private String alcoholBaseType; - private String cocktailImgUrl; - private String cocktailStory; + Long cocktailId, + String cocktailName, + String cocktailNameKo, + String alcoholStrength, + String cocktailType, + String alcoholBaseType, + String cocktailImgUrl, + String cocktailStory, + String cocktailPreview +){ + public static CocktailSearchResponseDto from(Cocktail cocktail){ + String preview =cocktail.getCocktailStory().length() >80 ? + cocktail.getCocktailStory().substring(0,80)+"..." + : cocktail.getCocktailStory(); - public CocktailSearchResponseDto(long cocktailId, String cocktailName, String cocktailNameKo, - String alcoholStrength, String cocktailType, - String alcoholBaseType, String cocktailImgUrl, - String cocktailStory) { - this.cocktailId = cocktailId; - this.cocktailName = cocktailName; - this.cocktailNameKo = cocktailNameKo; - this.alcoholStrength = alcoholStrength; - this.cocktailType = cocktailType; - this.alcoholBaseType = alcoholBaseType; - this.cocktailImgUrl = cocktailImgUrl; - this.cocktailStory = cocktailStory; + return new CocktailSearchResponseDto( + cocktail.getId(), + cocktail.getCocktailName(), + cocktail.getCocktailNameKo(), + cocktail.getAlcoholStrength().getDescription(), + cocktail.getCocktailType().getDescription(), + cocktail.getAlcoholBaseType().getDescription(), + cocktail.getCocktailImgUrl(), + cocktail.getCocktailStory(), + preview + ); } } \ No newline at end of file diff --git a/src/main/java/com/back/domain/cocktail/service/CocktailService.java b/src/main/java/com/back/domain/cocktail/service/CocktailService.java index 4865fce..050db8d 100644 --- a/src/main/java/com/back/domain/cocktail/service/CocktailService.java +++ b/src/main/java/com/back/domain/cocktail/service/CocktailService.java @@ -105,25 +105,12 @@ public List searchAndFilter(CocktailSearchRequestDto //Cocktail 엔티티 → CocktailResponseDto 응답 DTO로 바꿔주는 과정 List resultDtos = pageResult.stream() - .map(c -> new CocktailSearchResponseDto( - c.getId(), - c.getCocktailName(), - c.getCocktailNameKo(), - c.getAlcoholStrength().getDescription(), - c.getCocktailType().getDescription(), - c.getAlcoholBaseType().getDescription(), - c.getCocktailImgUrl(), - c.getCocktailStory() - )) + .map(CocktailSearchResponseDto::from) .collect(Collectors.toList()); return resultDtos; } -// private List nullIfEmpty(List list) { -// return CollectionUtils.isEmpty(list) ? null : list; -// } - // 칵테일 상세조회 @Transactional(readOnly = true) public CocktailDetailResponseDto getCocktailDetailById(Long cocktailId) { From e2ba7dbd75b758f8274d57d016969c4b4b4e3fb3 Mon Sep 17 00:00:00 2001 From: LeeKW Date: Wed, 8 Oct 2025 18:27:16 +0900 Subject: [PATCH 4/4] refactor : add getCocktails conditions --- .../controller/CocktailController.java | 7 ++- .../repository/CocktailRepository.java | 48 ++++++++++++++++-- .../cocktail/service/CocktailService.java | 50 ++++++++++--------- 3 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java index a389f05..6c2268a 100644 --- a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java +++ b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java @@ -31,6 +31,7 @@ public RsData getCocktailDetailById(@PathVariable lon return RsData.successOf(cocktailDetailResponseDto); } + // @param lastValue 다음 페이지에서 이 값보다 작은 항목만 가져오기 위해 사용 // @param lastId 마지막으로 가져온 칵테일 ID (첫 요청 null 가능) // @param size 가져올 데이터 개수 (기본값 DEFAULT_SIZE) // @return RsData 형태의 칵테일 요약 정보 리스트 @@ -38,10 +39,12 @@ public RsData getCocktailDetailById(@PathVariable lon @Transactional @Operation(summary = "칵테일 다건 조회") public RsData> getCocktails( + @RequestParam(value = "lastValue", required = false) Long lastValue, @RequestParam(value = "lastId", required = false) Long lastId, - @RequestParam(value = "size", required = false) Integer size + @RequestParam(value = "size", required = false) Integer size, + @RequestParam(value = "sortBy", required = false, defaultValue = "recent") String sortBy ) { - List cocktails = cocktailService.getCocktails(lastId, size); + List cocktails = cocktailService.getCocktails(lastValue, lastId, size, sortBy); return RsData.successOf(cocktails); } diff --git a/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java b/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java index 0695c46..a3973f9 100644 --- a/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java +++ b/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java @@ -16,14 +16,54 @@ @Repository public interface CocktailRepository extends JpaRepository { - // 첫 요청 → 최신순(내림차순)으로 정렬해서 가져오기 + // 전체조회 : 최신순 List findAllByOrderByIdDesc(Pageable pageable); - - // 무한스크롤 → lastId보다 작은 ID들 가져오기 List findByIdLessThanOrderByIdDesc(Long lastId, Pageable pageable); - List findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(String cocktailName, String ingredient); + // 전체 조회: keepsCount 기준 내림차순 + @Query(""" + SELECT c FROM Cocktail c + LEFT JOIN MyBar m ON m.cocktail = c AND m.status = 'ACTIVE' + GROUP BY c.id + ORDER BY COUNT(m) DESC, c.id DESC + """) + List findAllOrderByKeepCountDesc(Pageable pageable); + + // 무한스크롤 조회: lastKeepCount 이하 + @Query(""" + SELECT c FROM Cocktail c + LEFT JOIN MyBar m ON m.cocktail = c AND m.status = 'ACTIVE' + GROUP BY c.id + HAVING COUNT(m) < :lastKeepCount OR (COUNT(m) = :lastKeepCount AND c.id < :lastId) + ORDER BY COUNT(m) DESC, c.id DESC +""") + List findByKeepCountLessThanOrderByKeepCountDesc( + @Param("lastKeepCount") Long lastKeepCount, + @Param("lastId") Long lastId, + Pageable pageable + ); + + // 댓글순 + @Query("SELECT c FROM Cocktail c " + + "LEFT JOIN CocktailComment cm ON cm.cocktail = c " + + "GROUP BY c.id " + + "ORDER BY COUNT(cm) DESC, c.id DESC") + List findAllOrderByCommentsCountDesc(Pageable pageable); + + @Query(""" + SELECT c FROM Cocktail c + LEFT JOIN CocktailComment cm ON cm.cocktail = c + GROUP BY c.id + HAVING COUNT(cm) < :lastCommentsCount OR (COUNT(cm) = :lastCommentsCount AND c.id < :lastId) + ORDER BY COUNT(cm) DESC, c.id DESC + """) + List findByCommentsCountLessThanOrderByCommentsCountDesc( + @Param("lastCommentsCount") Long lastCommentsCount, + @Param("lastId") Long lastId, + Pageable pageable + ); + //검색, 필터 @Query("SELECT c FROM Cocktail c " + "WHERE (:keyword IS NULL OR :keyword = '' OR " + " LOWER(c.cocktailName) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " + diff --git a/src/main/java/com/back/domain/cocktail/service/CocktailService.java b/src/main/java/com/back/domain/cocktail/service/CocktailService.java index 050db8d..f302310 100644 --- a/src/main/java/com/back/domain/cocktail/service/CocktailService.java +++ b/src/main/java/com/back/domain/cocktail/service/CocktailService.java @@ -37,37 +37,41 @@ public Cocktail getCocktailById(Long id) { .orElseThrow(() -> new IllegalArgumentException("Cocktail not found. id=" + id)); } - // 칵테일 무한스크롤 조회 @Transactional(readOnly = true) - public List getCocktails(Long lastId, Integer size) { // 무한스크롤 조회, 클라이언트 쪽에서 lastId와 size 정보를 받음.(스크롤 이벤트) + public List getCocktails(Long lastValue, Long lastId, Integer size, String sortBy) { int fetchSize = (size != null) ? size : DEFAULT_SIZE; - + Pageable pageable = PageRequest.of(0, fetchSize); List cocktails; - if (lastId == null) { - // 첫 요청 → 최신 데이터부터 - cocktails = cocktailRepository.findAllByOrderByIdDesc(PageRequest.of(0, fetchSize)); - } else { - // 무한스크롤 → 마지막 ID보다 작은 데이터 조회 - cocktails = cocktailRepository.findByIdLessThanOrderByIdDesc(lastId, PageRequest.of(0, fetchSize)); + + switch (sortBy != null ? sortBy.toLowerCase() : "") { + case "keeps": + cocktails = (lastValue == null) + ? cocktailRepository.findAllOrderByKeepCountDesc(pageable) + : cocktailRepository.findByKeepCountLessThanOrderByKeepCountDesc(lastValue, lastId, pageable); + break; + case "comments": + cocktails = (lastValue == null) + ? cocktailRepository.findAllOrderByCommentsCountDesc(pageable) + : cocktailRepository.findByCommentsCountLessThanOrderByCommentsCountDesc(lastValue, lastId, pageable); + break; + default: + cocktails = (lastValue == null) + ? cocktailRepository.findAllByOrderByIdDesc(pageable) + : cocktailRepository.findByIdLessThanOrderByIdDesc(lastValue, pageable); + break; } + return cocktails.stream() - .map(c -> new CocktailSummaryResponseDto(c.getId(), c.getCocktailName(), c.getCocktailNameKo(), c.getCocktailImgUrl(), c.getAlcoholStrength().getDescription())) + .map(c -> new CocktailSummaryResponseDto( + c.getId(), + c.getCocktailName(), + c.getCocktailNameKo(), + c.getCocktailImgUrl(), + c.getAlcoholStrength().getDescription() + )) .collect(Collectors.toList()); } - // 칵테일 검색기능 - @Transactional(readOnly = true) - public List cocktailSearch(String keyword) { - // cockTailName, ingredient이 하나만 있을 수도 있고 둘 다 있을 수도 있음 - if (keyword == null || keyword.trim().isEmpty()) { - // 아무 검색어 없으면 전체 반환 처리 - return cocktailRepository.findAll(); - } else { - // 이름 또는 재료 둘 중 하나라도 매칭되면 결과 반환 - return cocktailRepository.findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(keyword, keyword); - } - } - // 칵테일 검색,필터기능 @Transactional(readOnly = true) public List searchAndFilter(CocktailSearchRequestDto cocktailSearchRequestDto) {