{"id":121,"date":"2020-03-13T17:41:59","date_gmt":"2020-03-13T08:41:59","guid":{"rendered":"http:\/\/www.skyer9.pe.kr\/wordpress\/?p=121"},"modified":"2020-03-28T20:20:48","modified_gmt":"2020-03-28T11:20:48","slug":"from-hello-to-querydsl-search-with-multi-params-5-12","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=121","title":{"rendered":"[From Hello To QueryDSL] Complex Search (5\/12)"},"content":{"rendered":"<h1>Complex Search<\/h1>\n<p>\uac80\uc0c9\uc870\uac74\uc774 \ud558\ub098 \ub610\ub294 \ub450\uac1c\ub85c \uace0\uc815\uc77c \uacbd\uc6b0, JPA \uc758 \ucffc\ub9ac\uc790\ub3d9\uc0dd\uc131 \uae30\ub2a5\uc73c\ub85c \ucc98\ub9ac\uac00 \uac00\ub2a5\ud569\ub2c8\ub2e4.<\/p>\n<p>\ud558\uc9c0\ub9cc, \uc2e4\ubb34\uc5d0\uc11c\ub294 1 ~ N \uac1c\uc758 \ubcf5\ud569 \uac80\uc0c9\uc870\uac74\uc744 \uc694\uad6c\ud558\ub294 \uacbd\uc6b0\uac00 \ub9e4\uc6b0 \ub9ce\uc544 JPA \ub9cc\uc73c\ub85c \uac10\ub2f9\uc774 \uc548\ub429\ub2c8\ub2e4. \uadf8\ub798\uc11c \uc4f0\uc774\ub294 \ub77c\uc774\ube0c\ub7ec\ub9ac \uc911\uc5d0 <code>querydsl<\/code> \uc744 \uc774\uc6a9\ud55c \ub2e4\uc774\ub098\ubbf9 \ucffc\ub9ac \uc0dd\uc131\uc744 \ud574\ubd05\ub2c8\ub2e4.<\/p>\n<h2>\uac1c\ubc1c\ud658\uacbd<\/h2>\n<ul>\n<li>Spring Boot 2.1.x<\/li>\n<li>Gradle 4.10.2<\/li>\n<\/ul>\n<p>\ud504\ub85c\uc81d\ud2b8\ub294 \uc774\uc804 \uae00\uc5d0\uc11c \uc791\uc131\ub41c \ud504\ub85c\uc81d\ud2b8\uc5d0 \ud30c\uc77c\uc744 \ucd94\uac00 \ub610\ub294 \uc218\uc815\ud558\ub294 \ubc29\uc2dd\uc73c\ub85c \uc9c4\ud589\ub429\ub2c8\ub2e4. \uc774\uc804 \uae00\uc744 \ub530\ub77c \ud558\uc9c0 \uc54a\uc740 \uacbd\uc6b0, \uba3c\uc800 \uc774\uc804 \uae00\ub300\ub85c \ud504\ub85c\uc81d\ud2b8\ub97c \uad6c\uc131\ud558\uc2dc\uae30 \ubc14\ub78d\ub2c8\ub2e4.<\/p>\n<h2>\ud30c\uc77c\ucd94\uac00 \ubc0f \uc218\uc815<\/h2>\n<p>querydsl \uc758 \uc815\uc0c1\uc801\uc778 \ucef4\ud30c\uc77c\uc744 \uc704\ud574 <code>build.gradle<\/code> \uc744 \uc544\ub798\uc640 \uac19\uc774 \uc218\uc815\ud569\ub2c8\ub2e4.<\/p>\n<p>build.gradle<\/p>\n<pre><code class=\"language-gradle\">buildscript {\n    ext {\n        springBootVersion = &#039;2.1.13.RELEASE&#039;\n    }\n    repositories {\n        mavenCentral()\n        jcenter()\n    }\n    dependencies {\n        classpath(&quot;org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}&quot;)\n    }\n}\n\napply plugin: &#039;java&#039;\napply plugin: &#039;war&#039;\napply plugin: &#039;org.springframework.boot&#039;\napply plugin: &#039;io.spring.dependency-management&#039;\n\ngroup &#039;kr.co.episode.example&#039;\nversion &#039;0.0.1-SNAPSHOT&#039;\nsourceCompatibility = 11\n\nrepositories {\n    mavenCentral()\n    jcenter()\n}\n\ndependencies {\n    compile(&#039;org.springframework.boot:spring-boot-starter-web&#039;)\n    compile(&#039;org.projectlombok:lombok&#039;)\n    compile(&#039;org.springframework.boot:spring-boot-starter-thymeleaf&#039;)\n    compile(&#039;com.querydsl:querydsl-jpa&#039;)\n    compile(&#039;com.querydsl:querydsl-apt&#039;)\n    compile(&#039;org.springframework.boot:spring-boot-starter-data-jpa&#039;)\n    compile(&#039;org.springframework.session:spring-session-jdbc&#039;)\n    testCompile(&#039;org.springframework.boot:spring-boot-starter-test&#039;)\n    testCompile(&quot;org.springframework.security:spring-security-test&quot;)\n}\n\n\/** QueryDSL Class Generate Script *\/\ndef generatedJavaSrcDir = &#039;src\/main\/generated&#039;\ndef queryDslOutput = file(generatedJavaSrcDir)\n\nsourceSets {\n    main {\n        java {\n            srcDirs = [&#039;src\/main\/java&#039;, generatedJavaSrcDir]\n        }\n    }\n}\n\n\/** QClass \uc0dd\uc131 *\/\ntask generateQueryDSL(type: JavaCompile, group: &#039;build&#039;) {\n    doFirst {\n        delete queryDslOutput\n        queryDslOutput.mkdirs()\n    }\n    source = sourceSets.main.java\n    classpath = configurations.compile\n    destinationDir = queryDslOutput\n    options.compilerArgs = [\n            &#039;-proc:only&#039;,\n            &#039;-processor&#039;,\n            &#039;com.querydsl.apt.jpa.JPAAnnotationProcessor&#039;\n    ]\n}\ncompileJava.dependsOn(generateQueryDSL)\n\n\/** clean \ud0dc\uc2a4\ud06c \uc2e4\ud589\uc2dc QClass \uc0ad\uc81c *\/\nclean {\n    delete queryDslOutput\n}<\/code><\/pre>\n<p>\ub450\uac1c\uc758 \uac80\uc0c9\uc870\uac74\uacfc \ud55c\uac1c\uc758 \uc815\ub82c\uc21c\uc11c\ub97c \uc804\ub2ec\ud560 \ud074\ub798\uc2a4\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.<\/p>\n<p>src\/main\/java\/kr\/co\/episode\/example\/domain\/posts\/PostsSearch.java<\/p>\n<pre><code class=\"language-java\">@Getter\npublic class PostsSearch {\n\n    private String title;\n    private String author;\n    private boolean orderBy;\n\n    public PostsSearch(String title, String author, boolean orderBy) {\n        this.title = title;\n        this.author = author;\n        this.orderBy = orderBy;\n    }\n}<\/code><\/pre>\n<p>\uc6b0\uc120 \ucee4\uc2a4\ud140 \uc778\ud130\ud398\uc774\uc2a4\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.<\/p>\n<p>src\/main\/java\/kr\/co\/episode\/example\/domain\/posts\/PostsPagingRepositoryCustom.java<\/p>\n<pre><code class=\"language-java\">public interface PostsPagingRepositoryCustom {\n\n    Page&lt;Posts&gt; search(Pageable pageable, PostsSearch postsSearch);\n}<\/code><\/pre>\n<p><code>QClass<\/code> \ub97c \ucc3e\uc9c0 \ubabb\ud558\uc9c0\ub9cc \ud504\ub85c\uc81d\ud2b8\ub294 \uc815\uc0c1\uc801\uc73c\ub85c \uc2e4\ud589\ub429\ub2c8\ub2e4. \uadf8\ub798\ub3c4 \uc624\ub958\ub97c \uc218\uc815\ud558\uace0 \uc2f6\uc73c\uba74 \uba54\ub274\uc5d0\uc11c <code>Build &gt; Rebuild Project<\/code> \ud558\uba74 \uc624\ub958\uac00 \uc0ac\ub77c\uc9d1\ub2c8\ub2e4.<\/p>\n<p>src\/main\/java\/kr\/co\/episode\/example\/domain\/posts\/PostsPagingRepositoryImpl.java<\/p>\n<pre><code class=\"language-java\">public class PostsPagingRepositoryImpl extends QuerydslRepositorySupport implements PostsPagingRepositoryCustom {\n\n    public PostsPagingRepositoryImpl() {\n        super(Posts.class);\n    }\n\n    @Override\n    public Page&lt;Posts&gt; search(Pageable pageable, PostsSearch postsSearch) {\n\n        QPosts posts = QPosts.posts;\n\n        JPQLQuery query = from(posts);\n\n        if (!&quot;&quot;.equals(postsSearch.getTitle())) {\n            query.where(posts.title.contains(postsSearch.getTitle()));\n        }\n\n        if (!&quot;&quot;.equals(postsSearch.getAuthor())) {\n            query.where(posts.author.contains(postsSearch.getAuthor()));\n        }\n\n        if (postsSearch.isOrderBy()) {\n            query.orderBy(posts.id.asc());\n        } else {\n            query.orderBy(posts.id.desc());\n        }\n\n        long totalCount = query.fetchCount();\n        List&lt;Posts&gt; results = getQuerydsl().applyPagination(pageable, query).fetch();\n\n        return new PageImpl&lt;&gt;(results, pageable, totalCount);\n    }\n}<\/code><\/pre>\n<p>src\/main\/java\/kr\/co\/episode\/example\/domain\/posts\/PostsPagingRepository.java<\/p>\n<pre><code class=\"language-java\">public interface PostsPagingRepository extends JpaRepository&lt;Posts, Long&gt;, PostsPagingRepositoryCustom {\n\n    \/\/ ......\n\n    Page&lt;Posts&gt; search(Pageable pageable, PostsSearch postsSearch);\n}<\/code><\/pre>\n<p>src\/main\/java\/kr\/co\/episode\/example\/web\/dto\/PostsSearchDto.java<\/p>\n<pre><code class=\"language-java\">@Getter\npublic class PostsSearchDto {\n\n    private String title;\n    private String author;\n    private boolean orderBy;\n\n    @Builder\n    public PostsSearchDto(String title, String author, boolean orderBy) {\n        this.title = title;\n        this.author = author;\n        this.orderBy = orderBy;\n    }\n\n    public PostsSearchDto(Map&lt;String, String&gt; params) {\n        this.title = params.get(&quot;title&quot;) == null ? &quot;&quot; : params.get(&quot;title&quot;);\n        this.author = params.get(&quot;author&quot;) == null ? &quot;&quot; : params.get(&quot;author&quot;);\n        this.orderBy = params.get(&quot;orderBy&quot;) != null;\n    }\n\n    public PostsSearch toEntity() {\n        return new PostsSearch(title, author, orderBy);\n    }\n}<\/code><\/pre>\n<p>src\/main\/java\/kr\/co\/episode\/example\/service\/posts\/PostsPagingService.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Service\npublic class PostsPagingService {\n\n    private final PostsPagingRepository postsPagingRepository;\n\n    \/\/ ......\n\n    @Transactional(readOnly = true)\n    public Page&lt;PostsListResponseDto&gt; search(Pageable pageable, PostsSearchDto postsSearchDto) {\n        Page&lt;Posts&gt; posts = postsPagingRepository.search(pageable, postsSearchDto.toEntity());\n        List&lt;PostsListResponseDto&gt; results = posts.getContent().stream()\n                .map(PostsListResponseDto::new)\n                .collect(Collectors.toList());\n        long totalCount = posts.getTotalElements();\n\n        return new PageImpl&lt;&gt;(results, pageable, totalCount);\n    }\n}<\/code><\/pre>\n<p>src\/main\/resources\/templates\/index.html<\/p>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;html xmlns:th=&quot;http:\/\/www.thymeleaf.org&quot;&gt;\n&lt;head&gt;\n    &lt;meta charset=&quot;UTF-8&quot;&gt;\n    &lt;title&gt;\uac8c\uc2dc\ud310&lt;\/title&gt;\n    &lt;link rel=&quot;stylesheet&quot; href=&quot;https:\/\/maxcdn.bootstrapcdn.com\/bootstrap\/3.3.2\/css\/bootstrap.min.css&quot;&gt;\n    &lt;style&gt;\n        .container p { display: inline }\n    &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body class=&quot;container&quot;&gt;\n\n&lt;h1&gt;\uc2a4\ud504\ub9c1 \ubd80\ud2b8 \uac8c\uc2dc\ud310&lt;\/h1&gt;\n\n&lt;div class=&quot;col-md-12&quot;&gt;\n    &lt;div class=&quot;row&quot;&gt;\n        &lt;div class=&quot;col-md-6&quot;&gt;\n            &lt;a href=&quot;\/posts\/save&quot; role=&quot;button&quot; class=&quot;btn&quot; btn-primary&gt;\uae00 \ub4f1\ub85d&lt;\/a&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/div&gt;\n\n&lt;div style=&quot;height: 80px;&quot;&gt;\n\n&lt;\/div&gt;\n\n&lt;table class=&quot;table&quot;&gt;\n    &lt;tr&gt;\n        &lt;th&gt;\uae00 \ubc88\ud638&lt;\/th&gt;\n        &lt;th&gt;\uae00\uc4f4\uc774&lt;\/th&gt;\n        &lt;th&gt;\uae00 \uc81c\ubaa9&lt;\/th&gt;\n        &lt;th&gt;\ucd5c\uc885\uc218\uc815&lt;\/th&gt;\n    &lt;\/tr&gt;\n    &lt;tr th:each=&quot;posts: ${posts}&quot;&gt;\n        &lt;td th:text=&quot;${posts.id}&quot;&gt;&lt;\/td&gt;\n        &lt;td&gt;\n            &lt;a th:href=&quot;${posts.link}&quot; th:text=&quot;${posts.author}&quot; \/&gt;\n        &lt;\/td&gt;\n        &lt;td&gt;\n            &lt;a th:href=&quot;${posts.link}&quot; th:text=&quot;${posts.title}&quot; \/&gt;\n        &lt;\/td&gt;\n        &lt;td th:text=&quot;${posts.modifiedDate}&quot;&gt;&lt;\/td&gt;\n    &lt;\/tr&gt;\n&lt;\/table&gt;\n\n&lt;div class=&quot;col-md-12&quot;&gt;\n    &lt;div class=&quot;col-md-4&quot;&gt;\n        &lt;form name=&quot;frm&quot;&gt;\n            &lt;input type=&quot;hidden&quot; name=&quot;page&quot; value=&quot;0&quot;&gt;\n            &lt;div class=&quot;form-group&quot;&gt;\n                &lt;label for=&quot;title&quot;&gt;\uc81c\ubaa9&lt;\/label&gt;\n                &lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;title&quot; placeholder=&quot;\uc81c\ubaa9\uc744 \uc785\ub825\ud558\uc138\uc694.&quot; name=&quot;title&quot; th:value=&quot;${search.title}&quot; \/&gt;\n            &lt;\/div&gt;\n            &lt;div class=&quot;form-group&quot;&gt;\n                &lt;label for=&quot;author&quot;&gt;\uc791\uc131\uc790&lt;\/label&gt;\n                &lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;author&quot; name=&quot;author&quot; placeholder=&quot;\uc791\uc131\uc790\ub97c \uc785\ub825\ud558\uc138\uc694.&quot; th:value=&quot;${search.author}&quot; \/&gt;\n            &lt;\/div&gt;\n            &lt;div class=&quot;custom-control custom-checkbox&quot;&gt;\n                &lt;input type=&quot;checkbox&quot; class=&quot;custom-control-input&quot; id=&quot;orderBy&quot; name=&quot;orderBy&quot; th:checked=&quot;${search.orderBy}&quot; \/&gt;\n                &lt;label class=&quot;custom-control-label&quot; for=&quot;orderBy&quot;&gt;\uae00\ubc88\ud638 \uc21c\uc73c\ub85c&lt;\/label&gt;\n            &lt;\/div&gt;\n        &lt;\/form&gt;\n\n        &lt;button type=&quot;button&quot; class=&quot;btn btn-primary&quot; id=&quot;btn-search&quot;&gt;\uac80\uc0c9&lt;\/button&gt;\n    &lt;\/div&gt;\n&lt;\/div&gt;\n\n&lt;nav style=&quot;text-align: center;&quot;&gt;\n    &lt;ul class=&quot;pagination&quot;\n        th:with=&quot;start=${T(Math).floor(posts.number\/10)*10},\n                    last=(${start + 9 &lt; posts.totalPages-1 ? start + 9 : posts.totalPages-1})&quot;&gt;\n        &lt;li&gt;\n            &lt;a th:onclick=&quot;|javascript:gotoPage(0)|&quot; aria-label=&quot;First&quot;&gt;\n                &lt;span aria-hidden=&quot;true&quot;&gt;First&lt;\/span&gt;\n            &lt;\/a&gt;\n        &lt;\/li&gt;\n\n        &lt;li th:class=&quot;${posts.first} ? &#039;disabled&#039;&quot;&gt;\n            &lt;a th:onclick=&quot;|javascript:gotoPage(${posts.number-1})|&quot; aria-label=&quot;Previous&quot;&gt;\n                &lt;span aria-hidden=&quot;true&quot;&gt;&lt;&lt;\/span&gt;\n            &lt;\/a&gt;\n        &lt;\/li&gt;\n\n        &lt;li th:each=&quot;page: ${#numbers.sequence(start, last)}&quot; th:class=&quot;${page == posts.number} ? &#039;active&#039;&quot;&gt;\n            &lt;a th:text=&quot;${page+1}&quot; th:onclick=&quot;|javascript:gotoPage(${page})|&quot; &gt;&lt;\/a&gt;\n        &lt;\/li&gt;\n\n        &lt;li th:class=&quot;${posts.last} ? &#039;disabled&#039;&quot;&gt;\n            &lt;a th:onclick=&quot;|javascript:gotoPage(${posts.number+1})|&quot; aria-label=&quot;Next&quot;&gt;\n                &lt;span aria-hidden=&quot;true&quot;&gt;&gt;&lt;\/span&gt;\n            &lt;\/a&gt;\n        &lt;\/li&gt;\n\n        &lt;li&gt;\n            &lt;a th:onclick=&quot;|javascript:gotoPage(${posts.totalPages-1})|&quot; aria-label=&quot;Last&quot;&gt;\n                &lt;span aria-hidden=&quot;true&quot;&gt;Last&lt;\/span&gt;\n            &lt;\/a&gt;\n        &lt;\/li&gt;\n    &lt;\/ul&gt;\n&lt;\/nav&gt;\n\n&lt;script src=&quot;https:\/\/code.jquery.com\/jquery-3.3.1.min.js&quot;&gt;&lt;\/script&gt;\n&lt;script src=&quot;https:\/\/stackpath.bootstrapcdn.com\/bootstrap\/4.3.1\/js\/bootstrap.min.js&quot;&gt;&lt;\/script&gt;\n\n&lt;script src=&quot;\/js\/app\/index.js&quot;&gt;&lt;\/script&gt;\n&lt;script&gt;\n    function gotoPage(pg) {\n        document.frm.page.value = pg;\n        document.frm.submit();\n    }\n&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<p>src\/main\/java\/kr\/co\/episode\/example\/web\/IndexController.java<\/p>\n<pre><code class=\"language-java\">    \/\/ ......\n    @GetMapping(&quot;\/&quot;)\n    public String index(Model model, @PageableDefault Pageable pageable, @RequestParam Map&lt;String, String&gt; params) {\n        pageable = PageRequest.of(pageable.getPageNumber(), 3);\n        PostsSearchDto postsSearchDto = new PostsSearchDto(params);\n\n        model.addAttribute(&quot;posts&quot;, postsPagingService.search(pageable, postsSearchDto));\n        model.addAttribute(&quot;search&quot;, postsSearchDto);\n\n        return &quot;index&quot;;\n    }\n    \/\/ ......<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>querydsl \uc744 \uc774\uc6a9\ud55c \ub2e4\uc774\ub098\ubbf9 \ucffc\ub9ac \uc0dd\uc131\uc744 \ud574\ubd05\ub2c8\ub2e4.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-121","post","type-post","status-publish","format-standard","hentry","category-spring-boot-2-1"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/121","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=121"}],"version-history":[{"count":16,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/121\/revisions"}],"predecessor-version":[{"id":341,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/121\/revisions\/341"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}