[SprintBoot] JPA / 검색 기능, Paging 기능 구현하기

doooly·2024년 2월 21일
0

SpringBoot

목록 보기
3/3

📄 Paging이란?

자세한 정보는 ⬇️아래 포스팅을 참고해주세요 !
https://velog.io/@dooo_it_ly/SpringBoot-Paging-구현

✅ Practice

프로젝트를 진행하면서, 간단한 검색 기능을 구현해보았습니다!

  • Portfolio search
  • Portfolio의 makeupName, info field에서 검색어 찾기

1️⃣ Entity

@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Portfolio extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "portfolio_id")
    private Long portfolioId;

    @ManyToOne
    @JoinColumn(name="user_id", nullable = false)
    private Artist artist;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Category category;

    @Column(nullable = false)
    private String makeupName;

    @Column(nullable = false)
    private int price;

    @Column(nullable = false)
    private String info;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "portfolio")
    private List<PortfolioImg> portfolioImgList;

    @Column
    private String averageStars;

    @Column(nullable = false, columnDefinition = "TINYINT(1) default 0")
    private boolean isBlock;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "portfolio")
    private List<Review> reviewList;



2️⃣ Controller

검색어, 페이지 번호, 정렬 기준을 인자로 받아 서비스에 넘겨줍니다
@PageDefault를 이용해 인자를 받는 방식도 존재하나, 페이지 크기를 임의로 지정해주었기 때문에 해당 방법을 사용했습니다

  @Operation(summary = "메이크업 검색", description = "메이크업을 검색/최근 검색어로 검색하는 API입니다.")
    @GetMapping("")
    public ApiResponse search(@RequestParam(value = "query") String query,
                              @RequestParam(value = "page", defaultValue = "0", required = false) int page,
                              @RequestParam(value = "sort", defaultValue = "desc") String sort){
        return ApiResponse.SuccessResponse(SuccessStatus.SEARCH_GET, searchService.search(query, page, sort));
    }



3️⃣ Service

  • Pageable 인스턴스 만들기
    인자로 받은 페이지 번호와 정렬 기준을 이용해 Pageable 인스턴스 생성
  • repository에서 검색어를 이용해 원하는 데이터 추출 -> Page<> 리턴
  • 가공한 PageDto로 결과 반환
 //검색
    public PortfolioPageDto search(String query, int page, String sortBy){
        Pageable pageable = setPageRequest(page, sortBy);

        //query 검색
        Page<Portfolio> portfolioPage = portfolioRepository.search(query, pageable);
        return PortfolioPageDto.from(portfolioPage);
    }

위의 코드에서 사용한 setPageRequest() 함수입니다

  • 정렬 기준 설정
    프로젝트에서 사용한 정렬 기준은 다음과 같습니다
    • 가격 높은 순
    • 가격 낮은 순
    • 별점 높은 순
    • 최근 생성 순
    따라서 인자로 들어오는 sortBy에 따라 정렬 기준을 설정한 후, 별점 높은 순 정렬을 추가로 설정해줍니다

  • PageRequest.of 를 사용해 Pageable 인스턴스 생성
    • 페이지 크기 30으로 지정
    • 페이지 번호, 페이지 크기, 정렬 기준of 메소드에 인자로 넘김
    //검색하기 정렬 기준 설정
    private Pageable setPageRequest(int page, String sortBy){

		//정렬 기준 설정
        Sort sort = switch (sortBy) {
            case "desc" -> Sort.by("price").descending();
            case "asc" -> Sort.by("price").ascending();
            case "review" -> Sort.by("averageStars").descending();
            case "recent" -> Sort.by("createdAt").descending();
            default -> throw new GlobalException(ErrorStatus.INVALID_SORT_CRITERIA);
        };

        //별점 높은 순 정렬 추가
        Sort finalSort = sort.and(Sort.by("averageStars").descending());
       
       return PageRequest.of(page, 30, finalSort);
    }



4️⃣ Repository

repository에서는 인자로 들어오는 queryPageable 인터페이스를 기반으로 Page<Portfolio>를 리턴합니다
현재 이 pageable에는 요청받은 페이지 번호와 페이지 크기, 정렬 기준이 설정되어 있습니다

LIKE를 이용해 makeUpNameInfo에서 검색어를 찾고 블락 상태가 아닌 포트폴리오만 필터링해 결과를 반환합니다

 @Query("SELECT p FROM Portfolio p " +
            "WHERE (p.makeupName LIKE %:query% OR p.info LIKE %:query%) " +
            "AND p.isBlock = false" )
    Page<Portfolio> search(@Param("query") String query, Pageable pageable);



5️⃣ PageDto

⬇️아래 포스팅에서 사용한 PageDto와 동일한 dto를 사용했습니다
https://velog.io/@dooo_it_ly/SpringBoot-Paging-구현

@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PortfolioPageDto {
    private List<PortfolioDto> content;
    private int currentPage; //현재 페이지 번호
    private int pageSize; //페이지 크기
    private int totalNumber; //전체 메이크업 개수
    private int totalPage; //전체 페이지 개수

    public static PortfolioPageDto from(Page<Portfolio> page){
        //검색 결과가 없을 시
        if(page.getContent().isEmpty())
            throw new GlobalException(ErrorStatus.SEARCH_NOT_FOUNT);

        List<PortfolioDto> content = page.stream()
                .map(PortfolioDto::from)
                .toList();

        return PortfolioPageDto.builder()
                .content(content)
                .pageSize(page.getSize())
                .currentPage(page.getNumber())
                .totalNumber(page.getNumberOfElements())
                .totalPage(page.getTotalPages())
                .build();
    }
}



6️⃣ Test

qeury를 p1으로 지정했을 때, p1, p11, p12 등 해당 검색어가 포함된 포트폴리오들이 리턴되는 것을 확인할 수 있습니다

  • 가격 낮은 순 정렬
  • 가격 높은 순 정렬
  • 최신 등록 순 정렬
{
    "result": "SUCCESS",
    "message": "조회가 완료되었습니다",
    "data": {
        "content": [
            {
                "portfolioId": 1,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p1",
                "price": 300000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 1,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 2,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 10,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p10",
                "price": 270000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 19,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 20,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 11,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p11",
                "price": 370000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 21,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 22,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 12,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p12",
                "price": 410000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 23,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 24,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 13,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p13",
                "price": 410000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 25,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 26,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 14,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p14",
                "price": 400000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 27,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 28,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 15,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p15",
                "price": 430000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 29,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 30,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 16,
                "category": "WEDDING",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p16",
                "price": 430000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 31,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 32,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 17,
                "category": "ETC",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p17",
                "price": 430000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 33,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 34,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 18,
                "category": "ETC",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p18",
                "price": 430000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 35,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 36,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            },
            {
                "portfolioId": 19,
                "category": "DAILY",
                "artistNickName": "Artist1",
                "userId": 1,
                "makeupName": "p19",
                "price": 230000,
                "makeupLocation": "SHOP",
                "shopLocation": "",
                "region": [
                    "JONGNO",
                    "DONGJAK"
                ],
                "isBlock": false,
                "averageStars": "0.00",
                "reviewCount": 0,
                "portfolioImgDtoList": [
                    {
                        "portfolioImgId": 37,
                        "portfolioImgSrc": "string",
                        "delete": false
                    },
                    {
                        "portfolioImgId": 38,
                        "portfolioImgSrc": "string2",
                        "delete": false
                    }
                ]
            }
        ],
        "currentPage": 0,
        "pageSize": 30,
        "totalNumber": 11,
        "totalPage": 1
    },
    "statusCode": 200
}

0개의 댓글