[ClassFlix] EP 18. QueryDSL 도입 (검색기능)
Project/ClassFlix

[ClassFlix] EP 18. QueryDSL 도입 (검색기능)

Querydsl의 꽃 검색기능을 도입합니다.

 

서비스 계층 설계

  1. 조건을 저장하고 전달하는 DTO를 생성합니다.
  2. 해당 dto의 인스턴스를 검색,페이징,정렬기능을 하는 리포지토리에 넘겨줍니다.
  3. where절에서 동적쿼리를 위한 Eq, Goe 메서드를 구현하고 넘겨받은 condition dto를 이용해 동적쿼리를 만들어 결과를 구합니다.

 

 

서비스 계층 구현

페이징과 정렬을 구현할때 동적쿼리는 거의 다 짜놔서 condition dto 생성과 where절 수정만 해주면 서비스 계층 구현은 될 것 같습니다.

 

LectureSearchCondition

package dongho.classflix.controller.dto;

import lombok.Data;

@Data
public class LectureSearchCondition {

    // 강의명, 강의자명, 별점(Goe)

    private String lectureName;
    private String teacherName;
    private Integer rating; // 실제는 double임. 안되면 수정필요

}

 

 

LectureRepositoryImpl

    @Override
    public Page<HomeLectureDto> searchPageSort(LectureSearchCondition condition, Pageable pageable) {
        JPAQuery<HomeLectureDto> query = queryFactory
                .select(new QHomeLectureDto(
                        lecture.id.as("lectureId"),
                        lecture.representImagePath,
                        lecture.lectureName.as("lectureName"),
                        lecture.averageRating
                ))
                .from(lecture)
                .where(
                        lectureNameEq(condition.getLectureName()),
                        teacherNameEq(condition.getTeacherName()),
                        ratingGoe(condition.getRating())
                )
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize());

        for (Sort.Order o : pageable.getSort()) {
            PathBuilder pathBuilder = new PathBuilder(lecture.getType(), lecture.getMetadata());
            query.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC,
                    pathBuilder.get(o.getProperty())));
        }

        QueryResults<HomeLectureDto> results = query.fetchResults();
        List<HomeLectureDto> content = results.getResults();
        long total = results.getTotal();

        return new PageImpl(content, pageable, total);
    }

    private BooleanExpression ratingGoe(Integer ratingGoe) {
        return ratingGoe != null ? lecture.averageRating.goe(ratingGoe) : null;
    }

    private BooleanExpression teacherNameEq(String teacherName) {
        return StringUtils.hasText(teacherName) ? lecture.teacherName.eq(teacherName) : null;
    }

    private BooleanExpression lectureNameEq(String lectureName) {
        return StringUtils.hasText(lectureName) ? lecture.lectureName.eq(lectureName) : null;
    }

 

 

searchTest

    @Test
    public void searchTest() throws Exception {
        //given
        Lecture lecture1 = new Lecture("스프링입문", "김영한", "좋아요");
        Lecture lecture2 = new Lecture("스프링입문", "김영한", "나빠요");
        Lecture lecture3 = new Lecture("jpa기초", "김동호", "그냥그래요");
        Lecture lecture4 = new Lecture("jpa활용", "김동호", "좋아요");
        em.persist(lecture1);
        em.persist(lecture2);
        em.persist(lecture3);
        em.persist(lecture4);

        //when
        LectureSearchCondition condition = new LectureSearchCondition();
        condition.setLectureName("스프링입문");
        PageRequest createdDate = PageRequest.of(0, 2, Sort.Direction.DESC, "createdDate");


        //then
        Page<HomeLectureDto> results = lectureRepository.searchPageSort(condition, createdDate);
        List<HomeLectureDto> content = results.getContent();
        assertAll("page : 0, size : 2, sort : createDate&DESC" +
                        "search : lectureName(스프링입문)",
                () -> assertEquals(content.get(0).getId(), lecture2.getId()),
                () -> assertEquals(content.get(1).getId(), lecture1.getId()));
    }

 

 

 

 

 

 

View 설계

  1. 디자인은 부트스트랩 검색창 템플릿에서 이쁜걸로 하나 찾아옵니다.
  2. JS를 이용해 search 버튼을 누르면 각 입력칸에 입력된 값들을 검색 파라미터 문자열로 만들어 링크를 만듭니다.

 

 

검색창 템플릿 원본

 

제 사이트에 맞게 몇개의 html과 css, js를 수정합니다. (디자인 눈갱 죄송합니다..)

별점에서 +,- 버튼을 누르면 0~5까지만 점수를 바꿀 수 있도록 합니다.

 

 

검색창에 해당하는 html

 

button은 클릭시 goSearch JS코드를 실행시키며 현재 page의 sortParam을 전달해줍니다.

    <div class="card card-7 text-center">
        <div class="card-body">
            <div class="input-group input--large">
                <label class="label">강의 이름</label>
                <input id="lectureName" class="input--style-1" type="text" placeholder="" name="lectureName">
            </div>
            <div class="input-group input--large">
                <label class="label">강의자 이름</label>
                <input id="teacherName" class="input--style-1" type="text" placeholder="" name="teacherName">
            </div>
            <div class="input-group input--medium">
                <label class="label">별 점</label>
                <div class="input-group-icon js-number-input">
                    <div class="icon-con">
                        <span class="plus">+</span>
                        <span class="minus">-</span>
                    </div>
                    <input id="reviewRating" class="input--style-1 quantity" type="text" name="guests" value="1점 이상만">
                </div>
            </div>
            <button th:onclick="goSearch([[${page.sortParam}]]);" class="btn-submit" type="submit">search</button>
        </div>
    </div>

 

goSearch()

    function goSearch(sortParam) {
        var searchParam = "/?";

        searchParam += "sort=" + sortParam;

        var lectureName = document.getElementById('lectureName').value;
        var teacherName = document.getElementById('teacherName').value;
        var rating = document.getElementById('reviewRating').value.substring(0,1);

        if (lectureName !== "") {
            searchParam += "&lectureName=" + lectureName;
        }
        if (teacherName !== "") {
            searchParam += "&teacherName=" + teacherName;
        }
        if (rating !== "") {
            searchParam += "&reviewRating=" + rating;
        }
        location.replace(searchParam);
    }

 

 

어플리케이션 구동 테스트

 

검색조건

  • 강의이름 : 스프링입문
  • 강의자이름 : 김영한
  • 별점 : 0점이상

 

0점 이상인 김영한님의 스프링 강의가 잘 나옵니다.

 

 

 

이번엔 검색조건을 바꾸어서 아무 강의도 나오지 않는 검색조건으로 검색해보겠습니다.

검색조건

  • 강의이름 : 스프링입문
  • 강의자이름 : 김영한
  • 별점 : 1점이상

 

 

결과가 똑같이 나옵니다. (잘못된 결과)

 

 

 

검색조건

  • 강의이름 : 스프링
  • 강의자이름 : 김영한
  • 별점 : 0점이상

 

검색결과가 없는것은 당연하지만 페이징부분에 이상한 0이 생겼습니다.

 

만약 검색결과가 없다면 "검색 결과가 없습니다"와 같은 문구를 출력하고 페이지부분은 없는게 좋을 것 같습니다.

 

 

 

 

또한 여기서 아무 페이지를 클릭한다면, 기존에 입력되어있던 검색조건이 없어지고 페이지와 정렬파라미터만 넘어갑니다.

페이지 0을 누르니까 -1페이지로 가는 모습

 

 

 

 

수정해야할 사항들

  • 별점계산부분 문제확인 (double과 Integer의 충돌의심)
  • 검색결과가 없을땐 "검색결과가 없습니다" 문구를 강의목록부분에 출력하기, 또한 페이지 선택창도 없애기
  • search를 눌러 화면이 바뀌어도 검색창에는 원래 검색했던 조건들이 그대로 남아있게 하기 (model에 condition을 담아 보내야될거같음)
  • 페이지를 눌러도 검색창에 있는 조건들도 파라미터로 같이 넘어가기
  • 위 모든 사항이 끝난 후 유지보수 연습을 위해 "사이트이름" 검색 조건을 추가합니다.

 

 

 

별점계산부분 오작동 해결

DTO의 멤버이름과 JS에서 문자열을 만들때 쓰는 이름과 달라서 발생했습니다.

 

LectureSearchCondition

 

별점에 해당하는 멤버변수의 이름은 rating으로 되어있습니다.

package dongho.classflix.controller.dto;

import lombok.Data;

@Data
public class LectureSearchCondition {

    // 강의명, 강의자명, 별점(Goe)

    private String lectureName;
    private String teacherName;
    private Integer rating; // 실제는 double임. 안되면 수정필요

}

 

 

goSearch

하지만 javascript에서는 reviewRating으로 쿼리파라미터를 넘기고있습니다.

이 부분을 reviewRating -> rating 으로 바꿔줍니다.

    function goSearch(sortParam) {
        var searchParam = "/?";

        searchParam += "sort=" + sortParam;

        var lectureName = document.getElementById('lectureName').value;
        var teacherName = document.getElementById('teacherName').value;
        var rating = document.getElementById('reviewRating').value.substring(0,1);

        if (lectureName !== "") {
            searchParam += "&lectureName=" + lectureName;
        }
        if (teacherName !== "") {
            searchParam += "&teacherName=" + teacherName;
        }
        if (rating !== "") {
            searchParam += "&reviewRating=" + rating;
        }
        // alert(searchParam);
        location.replace(searchParam);
        // return searchParam;
    }

 

 

 

이제는 별점 조건도 제대로 들어가는 것을 확인할 수 있습니다.

 

 

 

검색 결과가 없을 경우

타임리프의 if를 이용해서 해결해보려고 합니다.

 

lectures를 받아왔을 경우 강의들을 출력하고 받아오지 않았을 경우 "검색결과가 없습니다."를 출력합니다.

<div class="panel-body" th:unless="${not #strings.isEmpty(lectures)}">
    검색결과가 없습니다.
</div>

<div class="panel-body" th:if="${not #strings.isEmpty(lectures)}">
... 강의 나열 ...
</div>

<div class="text-center" th:if="${not #strings.isEmpty(lectures)}">
... 페이징 ...
<div>

 

 

그런데 잘 되지 않습니다.

강의 결과가 없다면 강의 목록대신 "검색결과가 없습니다." 만 화면에 나오고 페이지 숫자가 나오지 않아야합니다.

 

 

 

 

반대로 그럼 if -> unless, unless -> if로 바꿔보았습니다.

<div class="panel-body" th:if="${not #strings.isEmpty(lectures)}">
    검색결과가 없습니다.
</div>

<div class="panel-body" th:unless="${not #strings.isEmpty(lectures)}">
... 강의 나열 ...
</div>

<div class="text-center" th:unless="${not #strings.isEmpty(lectures)}">
... 페이징 ...
<div>

 

 

모든 결과가 출력되어야 하는 홈화면에서도 "검색결과가 없습니다."가 나오고,

 

조회결과가 없는 경우에도 "검색결과가 없습니다."만 나옵니다.

 

 

의심

그럼 querydsl에서 조회해올때 결과가 없어도 null으로 반환하지 않는걸까?

 

그런것 같습니다.

Content가 비어있으면 빈 리스트가 반환됩니다.

타임리프에서는 빈문자열인지, 그리고 null인지만 확인해서 true false를 판단하고있습니다.

 

 

두가지 선택지가 있습니다.

  1. 서비스(리포지토리)애서 Content가 비어있으면 null 반환
  2. 타임리프에서 null판단이 아닌 리스트 size로 판단

 

 

1번의 경우 Page도 사용하지 못할뿐 아니라 여러 코드를 뜯어고쳐야하지만 2번의 경우 한줄만 바꿔주면 됩니다.

 

그리고 검색하는 로직이 바뀌었으니 타임리프의 null체크부분을 바꿔주는 것은 당연한 것입니다.

기존 로직(findAll)은 결과가 없을 경우 null을 반환했지만 지금(searchPageSort)은 [](빈 리스트)를 반환합니다.

 

home.html 수정

<div class="panel-body" th:unless="${#lists.size(lectures) != 0}">
    검색결과가 없습니다.
</div>

<div class="panel-body" th:if="${#lists.size(lectures) != 0}">
... 강의 나열 ...
</div>

<div class="text-center" th:if="${#lists.size(lectures) != 0}">
... 페이징 ...
<div>

 

 

애플리케이션 테스트

 

조회결과가 아무 강의도 없는 쿼리를 날리면 "검색결과가 없습니다."가 정상적으로 출력됩니다.

 

 

 

 

검색조건 그대로 남기기

 

 

설계

  1. 홈 컨트롤러에서 conditoin dto를 모델에 넘겨줍니다.
  2. home.html에서 각 검색 창의 value값에 타임리프를 이용해 모델에서 넘겨받은 값을 넣어줍니다.

 

주의

  1. 홈화면에서는 검색창의 세개의 값 다 null이 들어있습니다.
  2. ratingGoe부분은 특정 문자열 "점 이상"을 붙여주어야 합니다.
  3. ratingGoe부분은 직접 입력하는 것이 아닌 +-로 입력해주기 때문에 null일 경우 다르게 해결해야합니다.

 

해결

ratingGoe를 생성자를 이용해 처음엔 0으로 세팅해주면 됩니다.

package dongho.classflix.controller.dto;

import lombok.Data;

@Data
public class LectureSearchCondition {

    // 강의명, 강의자명, 별점(Goe)

    private String lectureName;
    private String teacherName;
    private Integer ratingGoe; // 실제는 double임. 안되면 수정필요

    public LectureSearchCondition() {
        this.ratingGoe = 0;
    }
}

 

 

ratingGoe부분은 타임리프의 문자열 리터럴 입력형식을 이용해 "~점 이상" 문자열을 붙여줄 수 있습니다.

            <div class="input-group input--large">
                <label class="label">강의 이름</label>
                <input id="lectureName" class="input--style-1" th:value="${condition.lectureName}" type="text" placeholder="" name="lectureName">
            </div>
            <div class="input-group input--large">
                <label class="label">강의자 이름</label>
                <input id="teacherName" class="input--style-1" th:value="${condition.teacherName}" type="text" placeholder="" name="teacherName">
            </div>
            <div class="input-group input--medium">
                <label class="label">별 점</label>
                <div class="input-group-icon js-number-input">
                    <div class="icon-con">
                        <span class="plus">+</span>
                        <span class="minus">-</span>
                    </div>
                    <input id="ratingGoe" class="input--style-1 quantity" th:value="|${condition.ratingGoe}점 이상만|" type="text" name="guests" value="1점 이상만">
                </div>
            </div>

 

 

확인

첫 홈화면에서는 0점 이상만이라는 문구가 자동으로 나와있습니다.

null 값이 아닌 0이므로 +,- 버튼도 잘 동작합니다.

 

또, 검색을 하고난 이후에도 방금 검색했던 값들이 그대로 남아있습니다.

 

 

 

 

 

페이지를 누를때도 현재의 검색조건이 같이 넘어가기

 

설계

Prev, 페이지번호, Next 각 버튼을 다 하나의 JS코드를 이용해 다음링크로 이동하게 합니다.

 

<nav aria-label="Page navigation">
    <ul class="pagination pagination-sm">
        <li th:if="${page.isPrev()}" class="page-item"><a th:onclick="goPage([[${page.curPage-1}]],[[${page.sortParam}]]);" class="page-link" href="#">Prev</a></li>
        <li th:unless="${page.isPrev()}" class="page-item disabled"><a class="page-link">Prev</a></li>
        <li class="page-item" th:each="num, index : ${#numbers.sequence(page.startPage, page.endPage)}">
            <a th:onclick="goPage([[${index.current-1}]],[[${page.sortParam}]]);" th:text="${num}" class="page-link" href="">1</a>
        </li>
        <li th:if="${page.isNext()}" class="page-item"><a th:onclick="goPage([[${page.curPage+1}]],[[${page.sortParam}]]);" class="page-link" href="#">Next</a></li>
        <li th:unless="${page.isNext()}" class="page-item disabled"><a class="page-link">Next</a></li>
    </ul>
</nav>
    function getSearchParam(searchParam) {
        var lectureName = document.getElementById('lectureName').value;
        var teacherName = document.getElementById('teacherName').value;
        var ratingGoe = document.getElementById('ratingGoe').value.substring(0,1);

        if (lectureName !== "") {
            searchParam += "&lectureName=" + lectureName;
        }
        if (teacherName !== "") {
            searchParam += "&teacherName=" + teacherName;
        }
        if (ratingGoe !== "") {
            searchParam += "&ratingGoe=" + ratingGoe;
        }

        return searchParam;
    }
    
    
    function goPage(page, sortParam) {
        var param = "/?";
        param += "page=" + page;
        param += "&sort=" + sortParam;
        param = getSearchParam(param);

        alert(param);
        location.replace(param);
    }

 

alert로 파라미터 링크가 제대로 만들어져 있는지 확인해보겠습니다.

 

현재는 이름순 오름차순 정렬에 0점이상 서치조건입니다.

 

서치결과로는 현재 10페이지 이상 있습니다.

 

 

여기서 7페이지를 누르면 위와 같은 조건에서 page=6 만 추가된 링크로 이동해야합니다.

 

 

위와 같은 조건에서 page=6을 확인했습니다.

 

 

그런데 링크가 그대로입니다.

 

로그를 확인해 보아도 page=6이 넘어오지 않는 것을 확인할 수 있습니다.

 

 

이번엔 강제로 링크에다가 page=6을 넣어보겠습니다.

page request 로그와 강의결과를 보면 정상적으로 작동한 것을 볼 수 있습니다.

 

 

현재상황

  • JS코드로 파라미터 링크를 만들어 이동하는 것
  • href로 바로 이동하는것

위 둘의 결과는 같지만 JS코드로 만든 링크는 동작하지 않습니다.

 

 

 

의심

  • a 태그에서 onclick을 쓴 문제?
  • a 태그에서 href가 남아있는 상태에서 onclick을 쓴 문제?

 

a  태그에서 href 가 ""로 되어있는 상태에서 onclick상에서 location을 지정해도 a 태그의 href가 동작합니다.

 

 

해결

a태그에서 onclick을 사용해 링크를 지정할 때에는 href를 제거해주도록 합니다.

 

<ul class="pagination pagination-sm">
    <li th:if="${page.isPrev()}" class="page-item"><a th:onclick="goPage([[${page.curPage-1}]],[[${page.sortParam}]]);" class="page-link">Prev</a></li>
    <li th:unless="${page.isPrev()}" class="page-item disabled"><a class="page-link">Prev</a></li>
    <li class="page-item" th:each="num, index : ${#numbers.sequence(page.startPage, page.endPage)}">
        <a th:onclick="goPage([[${index.current-1}]],[[${page.sortParam}]]);" th:text="${num}" class="page-link">1</a>
    </li>
    <li th:if="${page.isNext()}" class="page-item"><a th:onclick="goPage([[${page.curPage+1}]],[[${page.sortParam}]]);" class="page-link">Next</a></li>
    <li th:unless="${page.isNext()}" class="page-item disabled"><a class="page-link">Next</a></li>
</ul>

 

 

 

 

 

 

"사이트 이름" 검색 조건 추가

 

 

설계

  1. condition dto 수정
  2. searchPageSort 수정
  3. 로직테스트
  4. home.html 수정
  5. 어플리케이션 동작확인

 

condiion dto

package dongho.classflix.controller.dto;

import lombok.Data;

@Data
public class LectureSearchCondition {

    // 강의명, 강의자명, 별점(Goe)

    private String lectureName;
    private String teacherName;
    private Integer ratingGoe; // 실제는 double임. 안되면 수정필요
    private String siteName;

    public LectureSearchCondition() {
        this.ratingGoe = 0;
    }
}

 

 

searchPageSort

    @Override
    public Page<HomeLectureDto> searchPageSort(LectureSearchCondition condition, Pageable pageable) {
        log.info("input rating : {}", condition.getRatingGoe());
        JPAQuery<HomeLectureDto> query = queryFactory
                .select(new QHomeLectureDto(
                        lecture.id.as("lectureId"),
                        lecture.representImagePath,
                        lecture.lectureName.as("lectureName"),
                        lecture.averageRating
                ))
                .from(lecture)
                .where(
                        lectureNameEq(condition.getLectureName()),
                        teacherNameEq(condition.getTeacherName()),
                        siteNameEq(condition.getSiteName()),
                        ratingGoe(condition.getRatingGoe())
                )
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize());

        for (Sort.Order o : pageable.getSort()) {
            PathBuilder pathBuilder = new PathBuilder(lecture.getType(), lecture.getMetadata());
            query.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC,
                    pathBuilder.get(o.getProperty())));
        }

        QueryResults<HomeLectureDto> results = query.fetchResults();
        List<HomeLectureDto> content = results.getResults();
        long total = results.getTotal();

        return new PageImpl(content, pageable, total);
    }

    private BooleanExpression ratingGoe(Integer ratingGoe) {
        return ratingGoe != null ? lecture.averageRating.goe(ratingGoe) : null;
    }

    private BooleanExpression teacherNameEq(String teacherName) {
        return StringUtils.hasText(teacherName) ? lecture.teacherName.eq(teacherName) : null;
    }

    private BooleanExpression siteNameEq(String siteName) {
        return StringUtils.hasText(siteName) ? lecture.siteName.eq(siteName) : null;
    }

    private BooleanExpression lectureNameEq(String lectureName) {
        return StringUtils.hasText(lectureName) ? lecture.lectureName.eq(lectureName) : null;
    }

 

 

로직테스트

    @Test
    public void searchPlusSiteName() throws Exception {
        //given
        URI uri = new URI("https://www.inflearn.com/");

//        Lecture(String lectureName, String teacherName, String content, String siteName, URI uri)
        Lecture lecture1 = new Lecture("스프링입문", "김영한", "좋아요", "인프런", uri);
        Lecture lecture2 = new Lecture("스프링입문", "김영한", "나빠요", "인프런", uri);
        Lecture lecture3 = new Lecture("jpa기초", "김동호", "그냥그래요", "클래스101", uri);
        Lecture lecture4 = new Lecture("jpa활용", "김동호", "좋아요", "클래스101", uri);
        em.persist(lecture1);
        em.persist(lecture2);
        em.persist(lecture3);
        em.persist(lecture4);


        //when
        LectureSearchCondition condition = new LectureSearchCondition();
        condition.setSiteName("인프런");
        PageRequest createdDate = PageRequest.of(0, 2, Sort.Direction.DESC, "createdDate");

        //then
        Page<HomeLectureDto> results = lectureRepository.searchPageSort(condition, createdDate);
        List<HomeLectureDto> content = results.getContent();
        assertAll("page : 0, size : 2, sort : createDate&DESC" +
                        "search : siteName(인프런)",
                () -> assertEquals(content.get(0).getId(), lecture2.getId()),
                () -> assertEquals(content.get(1).getId(), lecture1.getId()));

    }

 

 

 

 

home.html

 

검색창 추가

        <div class="card-body">
            <div class="input-group input--medium">
                <label class="label">사이트 이름</label>
                <input id="siteName" class="input--style-1" th:value="${condition.siteName}" type="text" placeholder="" name="siteName">
            </div>
            <div class="input-group input--large">
                <label class="label">강의 이름</label>
                <input id="lectureName" class="input--style-1" th:value="${condition.lectureName}" type="text" placeholder="" name="lectureName">
            </div>
            <div class="input-group input--medium">
                <label class="label">강의자 이름</label>
                <input id="teacherName" class="input--style-1" th:value="${condition.teacherName}" type="text" placeholder="" name="teacherName">
            </div>
            <div class="input-group input--medium">
                <label class="label">별 점</label>
                <div class="input-group-icon js-number-input">
                    <div class="icon-con">
                        <span class="plus">+</span>
                        <span class="minus">-</span>
                    </div>
                    <input id="ratingGoe" class="input--style-1 quantity" th:value="|${condition.ratingGoe}점 이상만|" type="text" name="guests" value="1점 이상만">
                </div>
            </div>
            <button th:onclick="goSearch([[${page.sortParam}]]);" class="btn-submit" type="submit">search</button>
        </div>

 

 

getSearchParam 수정

    function getSearchParam(searchParam) {
        var siteName = document.getElementById('siteName').value;
        var lectureName = document.getElementById('lectureName').value;
        var teacherName = document.getElementById('teacherName').value;
        var ratingGoe = document.getElementById('ratingGoe').value.substring(0,1);

        if (siteName !== "") {
            searchParam += "&siteName=" + siteName;
        }
        if (lectureName !== "") {
            searchParam += "&lectureName=" + lectureName;
        }
        if (teacherName !== "") {
            searchParam += "&teacherName=" + teacherName;
        }
        if (ratingGoe !== "") {
            searchParam += "&ratingGoe=" + ratingGoe;
        }

        return searchParam;
    }

 

 

 

 

 

어플리케이션 구동 테스트

 

 

InitDB상 인프런 결과 1개가 맞습니다.

 

 

 

Class Flix 로 넣어도 잘 나옵니다.

 

 

 

 

 

나름 조건 하나 추가하는데 시간이 별로 걸리지는 않았습니다.

동적쿼리나 dto, html 수정해주는 것은 당연한 것이고, searchParam 의 JS코드는 그나마 한곳에서만 검색조건 링크를 만들게 해놓아서 시간을 많이 줄인 것 같습니다.

 

이것 이상의 JS코드의 최적화는 프론트엔드에게 맡겨야겠습니다.

 

 

 

 

 

 

리팩토링 할 부분들

이제 짜잘하게 아쉽거나 리팩토링 할 부분들을 정리해보겠습니다.

 

  1. 현재 정렬조건이나 페이지 번호에 따라 view 상에서 이미 버튼이 눌려있는 표시
  2. 검색시에 단어 일부만 넣어도 검색되도록 하기
  3. 별점순 정렬조건 추가

 

 

 

버튼 눌림표시

그냥 간단하게 타임리프의 if, unless와 strings.equals 를 사용했습니다.

<div class="text-right">
    <a th:unless="${#strings.equals(page.sortParam, 'createdDate,DESC')}" class="btn btn-sm btn-default" role="button"
       th:href="@{/(page=${page.curPage},sort='createdDate,DESC')}" href="">최신순</a>
    <a th:if="${#strings.equals(page.sortParam, 'createdDate,DESC')}"
       class="btn btn-sm btn-default disabled" role="button">최신순</a>

    <a th:unless="${#strings.equals(page.sortParam, 'lectureName,ASC')}" class="btn btn-sm btn-default" role="button"
       th:href="@{/(page=${page.curPage},sort='lectureName,ASC')}" href="">이름순</a>
    <a th:if="${#strings.equals(page.sortParam, 'lectureName,ASC')}"
       class="btn btn-sm btn-default disabled" role="button" href="">이름순</a>
</div>

 

 

강의가 이름순일땐 이름순 비활성화

 

 

강의가 최신순일땐 최신순 비활성화

 

 

현재 페이지 번호(5)는 회색으로 보이게 해서 기존보다 더 쉽게 현재페이지로 인식할 수 있습니다.

<li class="page-item" th:each="num, index : ${#numbers.sequence(page.startPage, page.endPage)}">
    <a th:if="${#strings.equals(page.curPage+1, num)}" th:text="${num}" class="page-link" style="background-color: #eeeeee">1</a>
    <a th:unless="${#strings.equals(page.curPage+1, num)}" th:onclick="goPage([[${index.current-1}]],[[${page.sortParam}]]);" th:text="${num}" class="page-link">1</a>
</li>

 

 

 

단어 일부만 넣어도 검색가능

querydsl의 eq가 아닌 contains를 이용하면 됩니다.

    private BooleanExpression siteNameEq(String siteName) {
        return StringUtils.hasText(siteName) ? lecture.siteName.eq(siteName) : null;
    }

    private BooleanExpression siteNameContains(String siteName) {
        return StringUtils.hasText(siteName) ? lecture.siteName.contains(siteName) : null;
    }
.where(
        lectureNameEq(condition.getLectureName()),
        teacherNameEq(condition.getTeacherName()),
        siteNameContains(condition.getSiteName()),
        ratingGoe(condition.getRatingGoe())
)

 

 

인 으로만 검색해도 인프런이 나옵니다.

 

 

 

 

정렬조건에 별점순 추가