Criteria -> QueryDSL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•ด๋ณด๊ธฐ

๊ธฐ์กด ๊ฒ€์ƒ‰ API

/api/search/workbooks?type=name&criteria=date&order=desc&keyword=JAVA&start=0&size=10

  • type: name, tag, user ์ค‘ ํƒ 1 (๊ฒ€์ƒ‰ํ•  ๋•Œ ์ข…๋ฅ˜)
  • criteria: date, name, count, like ์ค‘ ํƒ 1 (์กฐํšŒํ•  ๋•Œ ์ •๋ ฌ ๊ธฐ์ค€)
  • order: asc (์˜ค๋ฆ„์ฐจ์ˆœ), desc (๋‚ด๋ฆผ์ฐจ์ˆœ)
  • keyword: ๊ฒ€์ƒ‰์–ด
  • start: ํŽ˜์ด์ง• ์‹œ์ž‘์ 
  • size: ๊ฐ€์ ธ์˜ฌ ๋ฌธ์ œ์ง‘ ๊ฐœ์ˆ˜

๋ณด๋˜๋ณด ๊ธฐ์กด ๊ฒ€์ƒ‰ api์ด๋‹ค.

๋ฌธ์ œ์ง‘ ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰, ํƒœ๊ทธ๋กœ ๊ฒ€์ƒ‰, ์œ ์ €๋ช…์œผ๋กœ ๊ฒ€์ƒ‰ ์ด๋ ‡๊ฒŒ 3๊ฐœ๋กœ ๋‚˜๋ˆ„์–ด์ ธ์žˆ์—ˆ๊ณ  ๊ฒ€์ƒ‰์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํƒœ๊ทธ๋‚˜ ์œ ์ €๋กœ ๊ฒ€์ƒ‰๋œ ๊ฒฐ๊ณผ์—์„œ ์ตœ์‹ ์ˆœ, ์ด๋ฆ„์ˆœ, ์นด๋“œ๊ฐœ์ˆ˜์ˆœ, ์ข‹์•„์š”์ˆœ์œผ๋กœ ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.

Untitled (8)


์ƒˆ๋กœ์šด ๊ฒ€์ƒ‰ API

/api/search/workbooks?keyword=JAVA&tags=1&users=1&criteria=date&start=0&size=10

  • criteria: date, name, count, like ์ค‘ ํƒ 1 (์กฐํšŒํ•  ๋•Œ ์ •๋ ฌ ๊ธฐ์ค€)
  • keyword: ๊ฒ€์ƒ‰์–ด
  • tags: ํƒœ๊ทธ id (์›ํ•˜๋Š” ํƒœ๊ทธ๋“ค๋กœ ํ•„ํ„ฐ๋ง)
  • users: ์œ ์ € id (์›ํ•˜๋Š” ์œ ์ €๋“ค๋กœ ํ•„ํ„ฐ๋ง)
  • start: ํŽ˜์ด์ง• ์‹œ์ž‘์ 
  • size: ๊ฐ€์ ธ์˜ฌ ๋ฌธ์ œ์ง‘ ๊ฐœ์ˆ˜

๋ณด๋˜๋ณด ์ƒˆ๋กœ์šด ๊ฒ€์ƒ‰ api์ด๋‹ค.

๋‹ค๋ฅธ ๊ฒ€์ƒ‰ ์‚ฌ์ดํŠธ + ํ”„๋กค๋กœ๊ทธ ๊ฒ€์ƒ‰์„ ์ฐธ๊ณ ํ–ˆ๋‹ค.

ํ”„๋กค๋กœ๊ทธ์˜ ํƒœ๊ทธ๋กœ ํ•„ํ„ฐ๋ง ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๋ณด๊ณ  ์šฐ๋ฆฌ๋„ ํƒœ๊ทธ ๋˜๋Š” ์œ ์ € ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ํ•„ํ„ฐ๋ง์œผ๋กœ ๋ฐ”๊พธ์ž๋Š” ์˜๊ฒฌ์ด ๋‚˜์™€์„œ ์ด๋ฅผ ์ ์šฉ์‹œํ‚ค๊ณ  ์‹ถ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฒ€์ƒ‰๋œ ๊ฒฐ๊ณผ์— ํƒœ๊ทธ๋‚˜ ์œ ์ €๋กœ ํ•„ํ„ฐ๋ง์„ ํ•˜๋Š” ๊ฒƒ์ด ๋ญ”๊ฐ€ ์–ด์ƒ‰ํ•œ ๊ฒƒ ๊ฐ™์•˜๊ณ  ์‹ค์ œ ๋‹ค๋ฅธ ๊ฒ€์ƒ‰ ์‚ฌ์ดํŠธ์—์„œ๋„ ๊ฒ€์ƒ‰๋œ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€์—์„œ ํ•„ํ„ฐ๋ง์„ ์ฃผ๋กœ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๊ฒ€์ƒ‰์–ด์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๊ฒ€์ƒ‰์„ ํ•œ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€์—์„œ ๋ณด์—ฌ์ฃผ๊ธฐ๋กœ ํ–ˆ์œผ๋ฉฐ ํƒœ๊ทธ, ์œ ์ € ๊ฒ€์ƒ‰์€ ์‚ญ์ œํ•˜๊ณ  ๋ฌธ์ œ์ง‘ ๊ฒ€์ƒ‰๋งŒ ๋‚จ๊ธฐ๊ธฐ๋กœ ํ–ˆ๋‹ค. ๋ฌธ์ œ์ง‘ ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ•ด๋‹น ๋ฌธ์ œ์ง‘๋“ค์˜ ํƒœ๊ทธ, ์œ ์ €๋กœ ํ•„ํ„ฐ๋ง์„ ํ•  ์ˆ˜ ์žˆ๊ณ  ์ตœ์‹ ์ˆœ, ์ด๋ฆ„์ˆœ, ์นด๋“œ๊ฐœ์ˆ˜์ˆœ, ์ข‹์•„์š”์ˆœ์œผ๋กœ ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ–ˆ๋‹ค.

Untitled (9)


Criteria? QueryDSL?

๊ธฐ์กด์˜ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์€ ๋™์  ์ฟผ๋ฆฌ ๊ตฌํ˜„์„ ์œ„ํ•ด JPQL ๋นŒ๋”๋กœ criteria๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  specification์„ ์ด์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์กฐ๊ฑด์„ ์ฒ˜๋ฆฌํ•˜์˜€๋‹ค.

Criteria

Criteria๋Š” JPQL์„ ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ๋นŒ๋” ํด๋ž˜์Šค API๋‹ค.

Criteria๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฌธ์ž๊ฐ€ ์•„๋‹Œ ์ฝ”๋“œ๋กœ JPQL์„ ์ž‘์„ฑํ•˜๋ฏ€๋กœ ๋ฌธ๋ฒ• ์˜ค๋ฅ˜๋ฅผ ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ์žก์„ ์ˆ˜ ์žˆ๊ณ  ๋ฌธ์ž ๊ธฐ๋ฐ˜์˜ JPQL๋ณด๋‹ค ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•˜๊ณ  ์žฅํ™ฉํ•ด์„œ ์ง๊ด€์ ์œผ๋กœ ์ดํ•ด๊ฐ€ ํž˜๋“ค๋‹ค๋Š” ๋‹จ์ ๋„ ์žˆ๋‹ค.

๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฌธ๋ฒ• ์˜ค๋ฅ˜๋ฅผ ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ์žก์„ ์ˆ˜ ์žˆ๋‹ค๊ณค ํ•˜์ง€๋งŒ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋ฉด ํ•„๋“œ๋ช…์„ ๋ฌธ์ž๋กœ ์ ์–ด์•ผ ํ•ด์„œ ์‹ค์ˆ˜๋ฅผ ํ•œ๋‹ค๋ฉด ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ์žก์„ ์ˆ˜๊ฐ€ ์—†๊ฒŒ ๋œ๋‹ค.

ํ™”๋ฉด ์บก์ฒ˜ 2021-09-27 120432

๋ฌผ๋ก  ์ด๋ฅผ ์œ„ํ•ด ๋ฉ”ํƒ€ ๋ชจ๋ธ API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

๋‹ค๋งŒ, ๋ฉ”ํƒ€ ๋ชจ๋ธ API๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ฉ”ํƒ€ ๋ชจํ…” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”๋ฐ ์ด๋Š” ์ฝ”๋“œ ์ž๋™ ์ƒ์„ฑ๊ธฐ๊ฐ€ ์žˆ์–ด ๋งŒ๋“ค์–ด ์ค€๋‹ค๊ณ  ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ์ฝ”๋“œ ์ƒ์„ฑ๊ธฐ๋Š” ๋นŒ๋“œ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‹คํ–‰ํ•œ๋‹ค.


Specification

Specification์€ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ์ถ”์ƒํ™”ํ•œ ๊ฐ์ฒด๋‹ค.

์ฆ‰, ๊ฒ€์ƒ‰ ์กฐ๊ฑด์— ๋Œ€ํ•ด Specification์„ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ์กฐ๊ฑด์˜ ๊ฒ€์ƒ‰์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ด๋‹ค.

์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋กœ ์˜ˆ๋ฅผ ๋“ค๋ฉด ์–ด๋–ค ํƒ€์ž…์˜ ๊ฒ€์ƒ‰์–ด์ธ์ง€ ์–ด๋–ค ์ˆœ์„œ๋กœ ์ •๋ ฌํ•  ๊ฒƒ์ธ์ง€๋ฅผ Criteria๋ฅผ ์ด์šฉํ•ด ๋งŒ๋“ค๊ณ  ์ด๋Ÿฌํ•œ ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ Specification์œผ๋กœ ์ƒ์„ฑํ•œ ๋’ค Repository ๋ฉ”์„œ๋“œ ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ์–ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค.


QueryDSL ํŠน์ง•

Criteria๋Š” ์œ„์—์„œ ๋งํ•œ ํŠน์ง•๋“ค์˜ ์žฅ์ ์„ ํ™•์‹คํ•˜๊ฒŒ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋„ˆ๋ฌด ๋ณต์žกํ•˜๊ณ  ์–ด๋ ต๋‹ค๋Š” ๊ฐ€์žฅ ํฐ ๋‹จ์ ์ด ์žˆ๋‹ค.

์ž‘์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๊ทธ ๋ณต์žก์„ฑ์œผ๋กœ ์ธํ•ด ์–ด๋–ค JPQL์ด ์ƒ์„ฑ๋ ์ง€ ํŒŒ์•…ํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š๋‹ค.

์ฟผ๋ฆฌ๋ฅผ ๋ฌธ์ž๊ฐ€ ์•„๋‹Œ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•ด๋„, ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•˜๋ฉฐ ๊ทธ ๋ชจ์–‘๋„ ์ฟผ๋ฆฌ์™€ ๋น„์Šทํ•˜๊ฒŒ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” JPQL ๋นŒ๋”๊ฐ€ ๋ฐ”๋กœ QueryDSL์ด๋‹ค.


Why use QueryDSL?

์ผ๋‹จ ์–ด๋–ค ๊ธฐ์ˆ ์„ ์ ์šฉํ•˜๊ธฐ ์ „์— ๋ฐ˜๋“œ์‹œ '์™œ ์ ์šฉํ•˜๋Š”๊ฐ€?' ์— ๋Œ€ํ•ด ์งš๊ณ  ๋„˜์–ด๊ฐ€์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์‚ฌ์‹ค ์ด๋ฏธ ์ค‘๊ฐ„๊ณฐ์ด๋ž‘ ํ”ผ์ผ€์ด๊ฐ€ Criteria, Specification์œผ๋กœ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์ž˜ ๊ตฌํ˜„ ํ•ด๋†“์•˜๊ธฐ์— ์ฒ˜์Œ์—๋Š” QueryDSL์„ ์ ์šฉํ•  ํ•„์š”๊ฐ€ ์žˆ์„๊นŒ ์‹ถ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๊ณฐ๊ณฐํžˆ ์ƒ๊ฐํ•œ ๋์— QueryDSL๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๊ทธ ์ด์œ ๋Š” ๋‘ ๊ฐ€์ง€์ด๋‹ค.

  1. ๊ธฐ์กด์˜ Criteria, Specification๋ฅผ ์ ์šฉํ•œ ์ฝ”๋“œ๋ฅผ ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•œ๊ฒŒ ์•„๋‹ˆ๊ธฐ์— ์–ด์ฐจํ”ผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ณต๋ถ€ํ–ˆ์–ด์•ผ ํ–ˆ๋‹ค. ๋‘˜ ๋‹ค ๊ทธ๋ ‡๊ฒŒ ํ•ด์•ผํ•˜๋Š”๊ฑฐ๋ฉด QueryDSL์„ ์ ์šฉํ•ด๋ณด๋Š” ๊ฒƒ๋„ ๊ดœ์ฐฎ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.
  2. ๊ฐ€๋…์„ฑ ๋ฐ ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๋‹ค.

์•„๋ฌด๋ž˜๋„ Criteria๋กœ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” ํ•œ๋ฒˆ์— ์˜๋ฏธ๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์› ๊ณ  ๋”ฐ๋ผ์„œ ๊ธฐ์กด ์ฝ”๋“œ์—์„œ ์ƒˆ๋กญ๊ฒŒ ๋ฐ”๋€ API๋Œ€๋กœ ์ ์šฉ์‹œํ‚ค๋Š”๊ฒŒ ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ค์šธ ๊ฒƒ ๊ฐ™์•˜๋‹ค.

๋˜ํ•œ ๋ฉ”ํƒ€ ๋ชจ๋ธ API๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ด์ƒ ๋ฌธ์ž์—ด ๊ทธ๋Œ€๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋‹ˆ ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ์˜ค๋ฅ˜๋ฅผ ์™„์ „ํžˆ ์žก์•„์ฃผ์ง€ ๋ชปํ•˜์˜€๋‹ค.

์ด๋Ÿฌํ•œ ์ด์œ ๋“ค๋กœ Criteria์—์„œ QueryDSL๋กœ ๋ณ€๊ฒฝํ•˜๋ฉฐ ์ƒˆ๋กœ์šด ๊ฒ€์ƒ‰ API๋ฅผ ์ ์šฉ์‹œํ‚ค๊ธฐ๋กœ ํ•˜์˜€๋‹ค.


QueryDSL ์„ค์ •

plugins {
		//1
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}

dependencies {
    //2
    implementation 'com.querydsl:querydsl-jpa'
}

//3
def querydslDir = "$buildDir/generated/querydsl"

//4
querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}

//5
sourceSets {
    main.java.srcDir querydslDir
}

//6
configurations {
    querydsl.extendsFrom compileClasspath
}

//7
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

๋นŒ๋“œํˆด๋กœ gradle์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

์‹ ๊ธฐํ•˜๊ฒŒ๋„ QueryDSL ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด maven์ด๋‚˜ ant๋กœ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‚˜์˜ค์ง€๋งŒ gradle์—์„œ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ž˜์„œ ๊ฒ€์ƒ‰์„ ํ†ตํ•ด ํ•ด๊ฒฐํ•˜์˜€๋‹ค.

์„ค์ •๋“ค์„ ์œ„์—์„œ๋ถ€ํ„ฐ ํ•˜๋‚˜์”ฉ ์„ค๋ช…ํ•˜์ž๋ฉด ์ด๋ ‡๋‹ค.

  1. Qํด๋ž˜์Šค ์ƒ์„ฑ์„ ์œ„ํ•œ QueryDSL ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
  2. QueryDSL ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
  3. Qํด๋ž˜์Šค๊ฐ€ ์ €์žฅ๋˜๋Š” ์œ„์น˜๋ฅผ ๋œปํ•œ๋‹ค. Qํด๋ž˜์Šค๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ํŒŒ์ผ๋“ค์ด๋ผ .gitignore์— ์ถ”๊ฐ€ํ•˜์—ฌ ๊นƒํ—™์— ์˜ฌ๋ฆฌ์ง€ ์•Š๋„๋ก ํ•˜๋Š”๊ฒƒ์ด ์ข‹์€๋ฐ build ๋””๋ ‰ํ† ๋ฆฌ์— ์žˆ๋Š” ํŒŒ์ผ๋“ค์€ ๋ชจ๋‘ ์˜ฌ๋ผ๊ฐ€์ง€ ์•Š๊ณ  ์žˆ๋Š” ์ค‘์ด๋ผ ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜์˜€๋‹ค.
  4. jpa true๋กœ ํ•˜๋ฉด ๋นŒ๋“œํ•  ๋•Œ ์ž๋™์œผ๋กœ Qํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค. querydslSourcesDir๋Š” Qํด๋ž˜์Šค๊ฐ€ ์–ด๋””์— ์ƒ์„ฑํ• ์ง€ ๊ฒฐ์ •ํ•œ๋‹ค.
  5. ๋นŒ๋“œํ•  ๋•Œ Qํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋œ ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  6. gradle 6.x ๋ฒ„์ „์—์„œ ์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ์ •์ƒ์ž‘๋™ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค. compile๋กœ ๊ฑธ๋ฆฐ JPA ์˜์กด์„ฑ์— ์ ‘๊ทผํ•˜๋„๋ก ํ•ด์ค€๋‹ค.
  7. annotation processor์˜ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ด์ฃผ์–ด ๋นŒ๋“œ ์‹œ Qํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋„๋ก ํ•ด์ค€๋‹ค.

complieQuerydsl์„ ์‹คํ–‰ํ•ด์ฃผ๋ฉด ์ด๋ ‡๊ฒŒ ์›ํ•˜๋Š” ์œ„์น˜์— Qํด๋ž˜์Šค๊ฐ€ ์ž˜ ์ƒ์„ฑ๋œ ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Untitled (10)


๊ธฐ์กด ์ฝ”๋“œ์™€ ๋ณ€๊ฒฝ๋œ ์ฝ”๋“œ ๋น„๊ต

@EqualsAndHashCode
@Getter
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class WorkbookSearchParameter {

    private static final int MINIMUM_START_PAGE = 0;
    private static final int DEFAULT_START_PAGE = 0;
    private static final int MINIMUM_PAGE_SIZE = 1;
    private static final int MAXIMUM_PAGE_SIZE = 100;
    private static final int DEFAULT_PAGE_SIZE = 20;

    private SearchKeyword searchKeyword;
    private SearchCriteria searchCriteria;
    private int start;
    private int size;

    private WorkbookSearchParameter(SearchCriteria searchCriteria, SearchKeyword searchKeyword, int start, int size) {
        this.start = start;
        this.size = size;
        this.searchKeyword = searchKeyword;
        this.searchCriteria = searchCriteria;
    }

    public static WorkbookSearchParameter ofRequest(String searchCriteria,
                                                    String searchKeyword,
                                                    String start, String size) {
        return of(
                SearchCriteria.of(searchCriteria),
                SearchKeyword.of(searchKeyword),
                initializeStartValue(start),
                initializeSizeValue(size)
        );
    }

    public static WorkbookSearchParameter of(SearchCriteria searchCriteria,
                                             SearchKeyword searchKeyword,
                                             int start, int size) {
        return new WorkbookSearchParameter(
                searchCriteria,
                searchKeyword,
                start,
                size
        );
    }

    private static int initializeStartValue(String start) {
        try {
            int value = Integer.parseInt(start);
            if (value < MINIMUM_START_PAGE) {
                throw new InvalidPageStartException();
            }
            return value;
        } catch (NumberFormatException e) {
            return DEFAULT_START_PAGE;
        }
    }

    private static int initializeSizeValue(String size) {
        try {
            int value = Integer.parseInt(size);
            if (value < MINIMUM_PAGE_SIZE || value > MAXIMUM_PAGE_SIZE) {
                throw new InvalidPageSizeException();
            }
            return value;
        } catch (NumberFormatException e) {
            return DEFAULT_PAGE_SIZE;
        }
    }

    public PageRequest toPageRequest() {
        return PageRequest.of(start, size);
    }
}

๊ธฐ์กด ์ฝ”๋“œ์—์„œ๋Š” ๊ฒ€์ƒ‰ ์กฐ๊ฑด ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ Specification์„ ์ด์šฉํ•ด ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค์ธ WorkbookSearchParameter์ด๋‹ค.

์—ฌ๊ธฐ์— ํƒ€์ž…, ์ •๋ ฌ ๊ธฐ์ค€ ๋“ฑ์„ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” SearchCriteria, SearchType, SearchOrder ๋“ฑ์ด ์žˆ๋‹ค.

@EqualsAndHashCode
@Getter
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class WorkbookSearchParameter {

    private static final int MINIMUM_START_PAGE = 0;
    private static final int DEFAULT_START_PAGE = 0;
    private static final int MINIMUM_PAGE_SIZE = 1;
    private static final int MAXIMUM_PAGE_SIZE = 100;
    private static final int DEFAULT_PAGE_SIZE = 20;

    private SearchKeyword searchKeyword;
    private SearchCriteria searchCriteria;
    private int start;
    private int size;

    @Builder
    private WorkbookSearchParameter(String searchCriteria, String searchKeyword, String start, String size) {
        this.start = initializeStartValue(start);
        this.size = initializeSizeValue(size);
        this.searchKeyword = SearchKeyword.of(searchKeyword);
        this.searchCriteria = SearchCriteria.of(searchCriteria);
    }

    private int initializeStartValue(String start) {
        try {
            int value = Integer.parseInt(start);
            if (value < MINIMUM_START_PAGE) {
                throw new InvalidPageStartException();
            }
            return value;
        } catch (NumberFormatException e) {
            return DEFAULT_START_PAGE;
        }
    }

    private int initializeSizeValue(String size) {
        try {
            int value = Integer.parseInt(size);
            if (value < MINIMUM_PAGE_SIZE || value > MAXIMUM_PAGE_SIZE) {
                throw new InvalidPageSizeException();
            }
            return value;
        } catch (NumberFormatException e) {
            return DEFAULT_PAGE_SIZE;
        }
    }

    public PageRequest toPageRequest() {
        return PageRequest.of(start, size);
    }
}
@RequiredArgsConstructor
@Repository
public class WorkbookSearchRepository {

    private final JPAQueryFactory jpaQueryFactory;

    public Page<Workbook> searchAll(WorkbookSearchParameter parameter,
                                    List<Long> tags,
                                    List<Long> users,
                                    Pageable pageable) {
        QueryResults<Workbook> results = queryBy(parameter, tags, users)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();
        return new PageImpl<>(results.getResults(), pageable, results.getTotal());
    }

    public List<Workbook> searchAll(WorkbookSearchParameter parameter) {
        return queryBy(parameter)
                .limit(parameter.getSize())
                .fetch();
    }

    private JPAQuery<Workbook> queryBy(WorkbookSearchParameter parameter) {
        return queryBy(parameter, Collections.emptyList(), Collections.emptyList());
    }

    private JPAQuery<Workbook> queryBy(WorkbookSearchParameter parameter, List<Long> tags, List<Long> users) {
        return jpaQueryFactory.selectFrom(workbook)
                .innerJoin(workbook.user, user).fetchJoin()
                .innerJoin(workbook.cards.cards, card)
                .leftJoin(workbook.workbookTags, workbookTag)
                .leftJoin(workbookTag.tag, tag)
                .leftJoin(workbook.hearts.hearts, heart)
                .where(containKeyword(parameter.getSearchKeyword()),
                        containTags(tags),
                        containUsers(users),
                        openedTrue())
                .groupBy(workbook.id)
                .orderBy(findCriteria(parameter.getSearchCriteria()), workbook.id.asc());
    }

    private BooleanExpression containKeyword(SearchKeyword searchKeyword) {
        if (searchKeyword == null) {
            return null;
        }
        String keyword = searchKeyword.getValue();
        return containsKeywordInWorkbookName(keyword)
                .or(equalsKeywordInWorkbookTag(keyword));
    }

    private BooleanExpression containsKeywordInWorkbookName(String keyword) {
        return workbook.name.lower().contains(keyword);
    }

    private BooleanExpression equalsKeywordInWorkbookTag(String keyword) {
        return workbook.workbookTags.any().tag.tagName.value.eq(keyword);
    }

    private BooleanExpression containTags(List<Long> tags) {
        if (tags == null || tags.isEmpty()) {
            return null;
        }
        return workbookTag
                .tag
                .id
                .in(tags);
    }

    private BooleanExpression containUsers(List<Long> users) {
        if (users == null || users.isEmpty()) {
            return null;
        }
        return user
                .id
                .in(users);
    }

    private BooleanExpression openedTrue() {
        return workbook.opened.isTrue();
    }

    private OrderSpecifier<?> findCriteria(SearchCriteria searchCriteria) {
        if (searchCriteria == SearchCriteria.DATE) {
            return workbook.createdAt.desc();
        }
        if (searchCriteria == SearchCriteria.NAME) {
            return workbook.name.asc();
        }
        if (searchCriteria == SearchCriteria.COUNT) {
            return card.countDistinct().desc();
        }
        return heart.countDistinct().desc();
    }
}

QueryDSL๋กœ ๋ฐ”๊พธ๋ฉด์„œ ๊ธฐ์กด์˜ WorkbookSearchParameter๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋“ค์–ด์˜จ ๊ฐ’๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ  ์ด ๊ฐ’์„ ์ด์šฉํ•ด QueryDSL ์ „์šฉ Repository์—์„œ ์กฐํšŒ๋ฅผ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

configuration์œผ๋กœ JPAQueryFactory๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ ๋’ค ์ด๋ฅผ ์ด์šฉํ•ด ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

๋””ํ…Œ์ผํ•œ ๋ถ€๋ถ„์€ ์„ค๋ช…ํ•˜๊ธฐ ์•„์ง ์ง€์‹์ด ๋ถ€์กฑํ•˜์ง€๋งŒ ๋Œ€๋žต์ ์œผ๋กœ JPAQueryFactory๋ฅผ ์‚ฌ์šฉํ•ด ๋™์  ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์˜จ ๊ฐ’์„ where ๋‚ด์— ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋“ค์„ ์ด์šฉํ•ด BooleanExpression์„ ๋ฐ˜ํ™˜๋ฐ›๋„๋ก ํ•˜์—ฌ ์กฐ๊ฑด์— ๋งž๊ฒŒ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋ฌผ๋ก  ์ด์™€ ํ•จ๊ป˜ paging๋„ ๊ฐ€๋Šฅํ•˜๋‹ค!

Criteria์™€ ๋น„๊ตํ•˜๋ฉด ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ€๋…์„ฑ์ด ์ข‹๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.


์ฃผ์˜ํ•  ์ 

์ ์šฉํ•˜๋‹ค ์ƒ๊ธด ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…์„ ๊ณต์œ ํ•œ๋‹ค.

  1. ์†Œ๋‚˜ํ๋ธŒ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋‹ˆ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•ด ๋นŒ๋“œํ•  ๋•Œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

    Qํด๋ž˜์Šค๋„ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒ€์ฆ์„ ํ•˜๋‹ค๋ณด๋‹ˆ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์˜€๋Š”๋ฐ ์ด๋ฅผ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒ€์ฆ์—์„œ Qํด๋ž˜์Šค๋ฅผ ์ œ์™ธ์‹œ์ผฐ๋‹ค.

    ์—ญ์‹œ๋‚˜ ๊ฒ€์ƒ‰์„ ํ•ด๋ณด๋‹ˆ ์ด๋Ÿฐ ์ƒํ™ฉ์„ ๋งž์ดํ•œ ๋ถ„๋“ค์ด ์žˆ์œผ์…”์„œ ์ด ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜์˜€๋‹ค. https://velog.io/@lxxjn0/์ฝ”๋“œ-๋ถ„์„-๋„๊ตฌ-์ ์šฉ๊ธฐ-2ํŽธ-JaCoCo-์ ์šฉํ•˜๊ธฐ

    jacocoTestCoverageVerification {
        def Qdomains = []
    
        for (qPattern in '*.QA'..'*.QZ') {
            Qdomains.add(qPattern + '*')
        }
    
        violationRules {
            rule {
                enabled = true
                element = 'CLASS'
    
                limit {
                    counter = 'BRANCH'
                    value = 'COVEREDRATIO'
                    minimum = 0.80
                }
    
                limit {
                    counter = 'LINE'
                    value = 'COVEREDRATIO'
                    minimum = 0.80
                }
    
                excludes = [
                        //์ œ์™ธ๋  ๋‹ค๋ฅธ ํด๋ž˜์Šค๋“ค
                ] + Qdomains
            }
        }
    }

  1. distinct์™€ orderBy๊ฐ€ ๋™์‹œ์— ์ ์šฉ๋˜์ง€ ์•Š๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

    ์ฐพ์•„๋ณด๋‹ˆ H2 ๋ฌธ๋ฒ• ๋ฌธ์ œ์˜€๊ณ  MySQL์—์„œ๋Š” ๊ฐ™์ด ์‚ฌ์šฉํ•ด๋„ ์ ์šฉ์ด ๋˜์—ˆ๋‹ค.

    ๊ทธ๋ž˜์„œ ์ผ๋‹จ์€ workbook์˜ id๋กœ groupBy๋ฅผ ํ•ด์„œ distinct๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ˆ˜์ •ํ–ˆ๋‹ค. ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ Flyway๋ฅผ ์ ์šฉํ•  ๋•Œ๋„ ๊ทธ๋ ‡๊ณ  ์ด๋ฒˆ์—๋„ ๊ทธ๋ ‡๊ณ  local๊ณผ test์—์„œ๋Š” H2๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  dev์™€ prod์—์„œ๋Š” Mariadb๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋‹ˆ H2์™€ MySQL์˜ ๋ฌธ๋ฒ• ์ฐจ์ด ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ ๋ฒˆ syntax ์—๋Ÿฌ๋ฅผ ๊ฒฝํ—˜ํ•˜๋Š” ์ผ์ด ๋งŽ์•˜๋‹ค.

    ๊ทธ๋ฆฌํ•˜์—ฌ ๋‹ค์Œ ์Šคํ”„๋ฆฐํŠธ ๋•Œ๋Š” ์ด ์ฐจ์ด๋ฅผ ํ•ด์†Œ์‹œํ‚ฌ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณผ ์ƒ๊ฐ์ด๋‹ค.


  1. ์ด๊ฑด ์šฐ๋ฆฌ๊ฐ€ ๊ฒช์€ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…์€ ์•„๋‹ˆ์ง€๋งŒ gradle์—์„œ QueryDSL ์„ค์ •๊ณผ ๊ด€๋ จํ•ด์„œ ๋งŽ์ด๋“ค ์ด์Šˆ๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ํ˜„์žฌ ์šฐ๋ฆฌ๊ฐ€ ์ ์šฉ์‹œํ‚จ ์„ค์ • ๋ฐฉ๋ฒ•์€ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•ด์„œ Qํด๋ž˜์Šค๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ์ด๋•Œ gradle ๋ฒ„์ „์ด ์—…๊ทธ๋ ˆ์ด๋“œ ๋จ์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฑท์–ด๋‚ด๊ธฐ ์œ„ํ•ด gradle AnnotationProcessor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ์ด๊ฒŒ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜๋„๋ก ํ•˜์ž.