Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

코딩블로그

[Spring] 검색기능-QueryDsl로 무한스크롤로 넘겨주기 본문

PopcornMate

[Spring] 검색기능-QueryDsl로 무한스크롤로 넘겨주기

_hanbxx_ 2024. 2. 14. 22:57
728x90

팝콘메이트에서는 검색 기능이 있는데, 프론트 분들이 무한 스크롤 형식으로 넘겨달라고 부탁하셔서 Querydsl 적용해보는 겸 구현해 보았다!

 

💖 Controller

 @GetMapping("/screenings/search")
    public SliceResponse<Screening> searchScreenings(
            @RequestParam(required = false,value = "title") String title,
            @RequestParam(required = false,value= "category") Category category,
            @ParameterObject @PageableDefault(size = 10) Pageable pageable
    ) {
        return screeningAdaptor.searchScreenings(title, category, pageable);
    }

 

 

💖 ScreeningAdaptor

    public SliceResponse<Screening> searchScreenings(String title, Category category, Pageable pageable) {
        return screeningRepository.querySliceScreening(title, category, pageable);
    }

 

💖 ScreeningRepositoryCustom

public interface ScreeningRepositoryCustom {
    SliceResponse<Screening> querySliceScreening(String title, Category  category, Pageable pageable);
    SliceResponse<Screening> querySliceScreeningByDate(String title, Category category, Pageable pageable);
}

(여기서 명명방식이 중요하다!! 이 형식으로 하지 않으면 인식하지 않게 되어 구현 불가능하다 )

 

💖 ScreeningRepositoryImpl

@RequiredArgsConstructor
public class ScreeningRepositoryImpl implements ScreeningRepositoryCustom {
    private final JPAQueryFactory queryFactory;
    @Override
    public SliceResponse<Screening> querySliceScreening(String title, Category category, Pageable pageable) {
        List<Screening> query = queryFactory.selectFrom(QScreening.screening)
                .where(
                        containsTitle(title),
                        hasCategory(category),
                        QScreening.screening.isPrivate.eq(false)
                )
                .orderBy(QScreening.screening.createdAt.desc()) // Adjust the sorting as needed
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize()+1)
                .fetch();

        return SliceResponse.of(SliceUtil.toSlice(query, pageable));
    }
}

 

여기까지는 모두가 다 똑같이 하셨을 것 같은데 나는 따로 SliceResponse, SliceUtil을 만들어서 프론트분들이 조금 더 쉽게 받아보실 수 있게 구현하였다

 

💖 SliceResponse

public record SliceResponse<T>(List<T> content, long page, int size, boolean hasNext) {
    public static <T> SliceResponse<T> of(Slice<T> slice) {
        return new SliceResponse<>(
                slice.getContent(),
                slice.getNumber(),
                slice.getNumberOfElements(),
                slice.hasNext());
    }
}

 

레코드는 클래스와 유사하지만, 간단한 데이터 전송 객체를 만드는 데 특화되어 있어서 record를 사용하였다

여기서 다양한 ResponseDto나 Entity를 받아들일 수 있도록 제네릭 클래스를 사용하였다. 제네릭 클래스는 다양한 데이터 타입을 수용할 수 있는 클래스이다. 즉, 클래스를 정의할 때 클래스 내부에서 사용되는 데이터 타입을 특정하지 않고, 어떤 타입이든지 사용할 수 있도록 유연하게 만드는 기능을 제공하는 것이다.

 

💖 SliceUtil

public class SliceUtil {
    public static <T> Slice<T> toSlice(List<T> contents, Pageable pageable) {
        boolean hasNext = hasNext(contents, pageable);
        return new SliceImpl<>(
                hasNext ? getContent(contents, pageable) : contents, pageable, hasNext);
    }

    // 다음 페이지 있는지 확인
    private static <T> boolean hasNext(List<T> content, Pageable pageable) {
        return pageable.isPaged() && content.size() > pageable.getPageSize();
    }

    // 데이터 1개 빼고 반환
    private static <T> List<T> getContent(List<T> content, Pageable pageable) {
        return content.subList(0, pageable.getPageSize());
    }
}

 

 

💖 스웨거상에서 요청할 수 있는 parameter

 

💖 요청 후 예시 Response

{
  "content": [
    {
      "createdAt": "2024-02-14T13:38:45.537Z",
      "updatedAt": "2024-02-14T13:38:45.537Z",
      "id": 0,
      "title": "string",
      "posterImgUrl": "string",
      "hostInfo": {
        "hostName": "string",
        "hostPhoneNumber": "string",
        "hostEmail": "string"
      },
      "positiveCount": {
        "cineMaster": 0,
        "greatFilming": 0,
        "pom": 0,
        "animationIsGood": 0,
        "artIsGood": 0,
        "setIsArt": 0,
        "custom": 0,
        "music": 0,
        "ost": 0,
        "writtenByGod": 0,
        "topicIsGood": 0,
        "linesAreGood": 0,
        "endingIsGood": 0,
        "castingIsGood": 0,
        "actingIsGood": 0,
        "chemistryIsGood": 0
      },
      "negativeCount": {
        "iffy": 0,
        "badEditing": 0,
        "badAngle": 0,
        "badDetail": 0,
        "badColor": 0,
        "badCustom": 0,
        "badMusic": 0,
        "badSound": 0,
        "badEnding": 0,
        "endingLoose": 0,
        "noDetail": 0,
        "badTopic": 0,
        "badActing": 0,
        "badCasting": 0
      },
      "month": "2024-02-14T13:38:45.537Z",
      "screeningStartDate": "2024-02-14T13:38:45.537Z",
      "screeningEndDate": "2024-02-14T13:38:45.537Z",
      "screeningStartTime": "2024-02-14T13:38:45.537Z",
      "location": "string",
      "participationUrl": "string",
      "information": "string",
      "hasAgreed": true,
      "category": "GRADUATE",
      "screeningRate": 0,
      "movieReviewCountNeg": 0,
      "movieReviewCountPos": 0,
      "locationCountNeg": 0,
      "locationCountPos": 0,
      "serviceCountNeg": 0,
      "serviceCountPos": 0,
      "private": true
    }
  ],
  "page": 0,
  "size": 0,
  "hasNext": true
}
728x90