๊ธฐ์กด ๊ฒ์ 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๊ฐ๋ก ๋๋์ด์ ธ์์๊ณ ๊ฒ์์ ์ค์๊ฐ์ผ๋ก ํ ์ ์์๋ค.
ํ๊ทธ๋ ์ ์ ๋ก ๊ฒ์๋ ๊ฒฐ๊ณผ์์ ์ต์ ์, ์ด๋ฆ์, ์นด๋๊ฐ์์, ์ข์์์์ผ๋ก ์ ๋ ฌ์ด ๊ฐ๋ฅํ๋ค.
์๋ก์ด ๊ฒ์ 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์ด๋ค.
๋ค๋ฅธ ๊ฒ์ ์ฌ์ดํธ + ํ๋กค๋ก๊ทธ ๊ฒ์์ ์ฐธ๊ณ ํ๋ค.
ํ๋กค๋ก๊ทธ์ ํ๊ทธ๋ก ํํฐ๋ง ํ๋ ๊ธฐ๋ฅ์ ๋ณด๊ณ ์ฐ๋ฆฌ๋ ํ๊ทธ ๋๋ ์ ์ ๊ฒ์ ๊ธฐ๋ฅ์ ํํฐ๋ง์ผ๋ก ๋ฐ๊พธ์๋ ์๊ฒฌ์ด ๋์์ ์ด๋ฅผ ์ ์ฉ์ํค๊ณ ์ถ์๋ค.
ํ์ง๋ง ์ค์๊ฐ์ผ๋ก ๊ฒ์๋ ๊ฒฐ๊ณผ์ ํ๊ทธ๋ ์ ์ ๋ก ํํฐ๋ง์ ํ๋ ๊ฒ์ด ๋ญ๊ฐ ์ด์ํ ๊ฒ ๊ฐ์๊ณ ์ค์ ๋ค๋ฅธ ๊ฒ์ ์ฌ์ดํธ์์๋ ๊ฒ์๋ ๊ฒฐ๊ณผ ํ์ด์ง์์ ํํฐ๋ง์ ์ฃผ๋ก ํ๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ ์ ์์๋ค.
๊ทธ๋์ ๊ฒ์์ด์ ๋ํ ๊ฒฐ๊ณผ๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ์๋ ๊ฒ์์ ํ ๊ฒฐ๊ณผ ํ์ด์ง์์ ๋ณด์ฌ์ฃผ๊ธฐ๋ก ํ์ผ๋ฉฐ ํ๊ทธ, ์ ์ ๊ฒ์์ ์ญ์ ํ๊ณ ๋ฌธ์ ์ง ๊ฒ์๋ง ๋จ๊ธฐ๊ธฐ๋ก ํ๋ค. ๋ฌธ์ ์ง ์ด๋ฆ์ผ๋ก ๊ฒ์๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ์ผ๋ก ํด๋น ๋ฌธ์ ์ง๋ค์ ํ๊ทธ, ์ ์ ๋ก ํํฐ๋ง์ ํ ์ ์๊ณ ์ต์ ์, ์ด๋ฆ์, ์นด๋๊ฐ์์, ์ข์์์์ผ๋ก ์ ๋ ฌ์ด ๊ฐ๋ฅํ๋๋ก ํ๋ค.
Criteria? QueryDSL?
๊ธฐ์กด์ ๊ฒ์ ๊ธฐ๋ฅ์ ๋์ ์ฟผ๋ฆฌ ๊ตฌํ์ ์ํด JPQL ๋น๋๋ก criteria๋ฅผ ์ฌ์ฉํ๊ณ specification์ ์ด์ฉํ์ฌ ์ฟผ๋ฆฌ ์กฐ๊ฑด์ ์ฒ๋ฆฌํ์๋ค.
Criteria
Criteria๋ JPQL์ ์๋ฐ ์ฝ๋๋ก ์์ฑํ๋๋ก ๋์์ฃผ๋ ๋น๋ ํด๋์ค API๋ค.
Criteria๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฌธ์๊ฐ ์๋ ์ฝ๋๋ก JPQL์ ์์ฑํ๋ฏ๋ก ๋ฌธ๋ฒ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ๋จ๊ณ์์ ์ก์ ์ ์๊ณ ๋ฌธ์ ๊ธฐ๋ฐ์ JPQL๋ณด๋ค ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ ํ๊ฒ ์์ฑํ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค. ํ์ง๋ง ์ฝ๋๊ฐ ๋ณต์กํ๊ณ ์ฅํฉํด์ ์ง๊ด์ ์ผ๋ก ์ดํด๊ฐ ํ๋ค๋ค๋ ๋จ์ ๋ ์๋ค.
๋ฟ๋ง ์๋๋ผ ๋ฌธ๋ฒ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ๋จ๊ณ์์ ์ก์ ์ ์๋ค๊ณค ํ์ง๋ง ์ฌ์ฉํ๋ค๋ณด๋ฉด ํ๋๋ช ์ ๋ฌธ์๋ก ์ ์ด์ผ ํด์ ์ค์๋ฅผ ํ๋ค๋ฉด ์ปดํ์ผ ๋จ๊ณ์์ ์ก์ ์๊ฐ ์๊ฒ ๋๋ค.
๋ฌผ๋ก ์ด๋ฅผ ์ํด ๋ฉํ ๋ชจ๋ธ API๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค๊ณ ํ๋ค.
๋ค๋ง, ๋ฉํ ๋ชจ๋ธ API๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ฉํ ๋ชจํ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ผ ํ๋๋ฐ ์ด๋ ์ฝ๋ ์๋ ์์ฑ๊ธฐ๊ฐ ์์ด ๋ง๋ค์ด ์ค๋ค๊ณ ํ๋ค. ์ด๋ฌํ ์ฝ๋ ์์ฑ๊ธฐ๋ ๋น๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํด์ ์คํํ๋ค.
Specification
Specification์ ๊ฒ์ ์กฐ๊ฑด์ ์ถ์ํํ ๊ฐ์ฒด๋ค.
์ฆ, ๊ฒ์ ์กฐ๊ฑด์ ๋ํด Specification์ ์์ฑํ๊ณ , ์ด๋ฅผ ํตํด ๋ค์ํ ์กฐ๊ฑด์ ๊ฒ์์ ํ ์ ์๋ค๋ ๋ป์ด๋ค.
์ฐ๋ฆฌ ํ๋ก์ ํธ๋ก ์๋ฅผ ๋ค๋ฉด ์ด๋ค ํ์ ์ ๊ฒ์์ด์ธ์ง ์ด๋ค ์์๋ก ์ ๋ ฌํ ๊ฒ์ธ์ง๋ฅผ Criteria๋ฅผ ์ด์ฉํด ๋ง๋ค๊ณ ์ด๋ฌํ ๋ค์ํ ๊ฒ์ ์กฐ๊ฑด์ Specification์ผ๋ก ์์ฑํ ๋ค Repository ๋ฉ์๋ ์ธ์๋ก ๋๊ฒจ์ฃผ์ด์ ์ฌ์ฉํ๋ค.
QueryDSL ํน์ง
Criteria๋ ์์์ ๋งํ ํน์ง๋ค์ ์ฅ์ ์ ํ์คํ๊ฒ ๊ฐ์ง๊ณ ์๋ค.
ํ์ง๋ง ๋๋ฌด ๋ณต์กํ๊ณ ์ด๋ ต๋ค๋ ๊ฐ์ฅ ํฐ ๋จ์ ์ด ์๋ค.
์์ฑ๋ ์ฝ๋๋ฅผ ๋ณด๋ฉด ๊ทธ ๋ณต์ก์ฑ์ผ๋ก ์ธํด ์ด๋ค JPQL์ด ์์ฑ๋ ์ง ํ์ ํ๊ธฐ๊ฐ ์ฝ์ง ์๋ค.
์ฟผ๋ฆฌ๋ฅผ ๋ฌธ์๊ฐ ์๋ ์ฝ๋๋ก ์์ฑํด๋, ์ฝ๊ณ ๊ฐ๊ฒฐํ๋ฉฐ ๊ทธ ๋ชจ์๋ ์ฟผ๋ฆฌ์ ๋น์ทํ๊ฒ ๊ฐ๋ฐํ ์ ์๋ JPQL ๋น๋๊ฐ ๋ฐ๋ก QueryDSL์ด๋ค.
Why use QueryDSL?
์ผ๋จ ์ด๋ค ๊ธฐ์ ์ ์ ์ฉํ๊ธฐ ์ ์ ๋ฐ๋์ '์ ์ ์ฉํ๋๊ฐ?' ์ ๋ํด ์ง๊ณ ๋์ด๊ฐ์ผ ํ๋ค๊ณ ์๊ฐํ๋ค.
์ฌ์ค ์ด๋ฏธ ์ค๊ฐ๊ณฐ์ด๋ ํผ์ผ์ด๊ฐ Criteria, Specification์ผ๋ก ๊ฒ์ ๊ธฐ๋ฅ์ ์ ๊ตฌํ ํด๋์๊ธฐ์ ์ฒ์์๋ QueryDSL์ ์ ์ฉํ ํ์๊ฐ ์์๊น ์ถ์๋ค.
ํ์ง๋ง ๊ณฐ๊ณฐํ ์๊ฐํ ๋์ QueryDSL๋ก ๋ณ๊ฒฝํ๊ธฐ๋ก ํ๋ค.
๊ทธ ์ด์ ๋ ๋ ๊ฐ์ง์ด๋ค.
- ๊ธฐ์กด์ Criteria, Specification๋ฅผ ์ ์ฉํ ์ฝ๋๋ฅผ ๋ด๊ฐ ๊ตฌํํ๊ฒ ์๋๊ธฐ์ ์ด์ฐจํผ ์ฒ์๋ถํฐ ๊ณต๋ถํ์ด์ผ ํ๋ค. ๋ ๋ค ๊ทธ๋ ๊ฒ ํด์ผํ๋๊ฑฐ๋ฉด QueryDSL์ ์ ์ฉํด๋ณด๋ ๊ฒ๋ ๊ด์ฐฎ๊ฒ ๋ค๊ณ ์๊ฐํ๋ค.
- ๊ฐ๋ ์ฑ ๋ฐ ์ปดํ์ผ ์ค๋ฅ๋ค.
์๋ฌด๋๋ 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์์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ๋์ค์ง ์๋๋ค. ๊ทธ๋์ ๊ฒ์์ ํตํด ํด๊ฒฐํ์๋ค.
์ค์ ๋ค์ ์์์๋ถํฐ ํ๋์ฉ ์ค๋ช ํ์๋ฉด ์ด๋ ๋ค.
- Qํด๋์ค ์์ฑ์ ์ํ QueryDSL ํ๋ฌ๊ทธ์ธ์ ์ถ๊ฐํ๋ค.
- QueryDSL ์์กด์ฑ์ ์ถ๊ฐํ๋ค.
- Qํด๋์ค๊ฐ ์ ์ฅ๋๋ ์์น๋ฅผ ๋ปํ๋ค. Qํด๋์ค๋ ์๋์ผ๋ก ์์ฑ๋๋ ํ์ผ๋ค์ด๋ผ .gitignore์ ์ถ๊ฐํ์ฌ ๊นํ์ ์ฌ๋ฆฌ์ง ์๋๋ก ํ๋๊ฒ์ด ์ข์๋ฐ build ๋๋ ํ ๋ฆฌ์ ์๋ ํ์ผ๋ค์ ๋ชจ๋ ์ฌ๋ผ๊ฐ์ง ์๊ณ ์๋ ์ค์ด๋ผ ์ด๋ ๊ฒ ์ค์ ํ์๋ค.
- jpa true๋ก ํ๋ฉด ๋น๋ํ ๋ ์๋์ผ๋ก Qํด๋์ค๊ฐ ์์ฑ๋๋ค. querydslSourcesDir๋ Qํด๋์ค๊ฐ ์ด๋์ ์์ฑํ ์ง ๊ฒฐ์ ํ๋ค.
- ๋น๋ํ ๋ Qํด๋์ค๊ฐ ์์ฑ๋ ์์น๋ฅผ ๋ํ๋ธ๋ค.
- gradle 6.x ๋ฒ์ ์์ ์ด ์ฝ๋๋ฅผ ์์ฑํด์ผ ์ ์์๋ํ๋ค๊ณ ํ๋ค. compile๋ก ๊ฑธ๋ฆฐ JPA ์์กด์ฑ์ ์ ๊ทผํ๋๋ก ํด์ค๋ค.
- annotation processor์ ๊ฒฝ๋ก๋ฅผ ์ค์ ํด์ฃผ์ด ๋น๋ ์ Qํด๋์ค๊ฐ ์์ฑ๋๋๋ก ํด์ค๋ค.
complieQuerydsl์ ์คํํด์ฃผ๋ฉด ์ด๋ ๊ฒ ์ํ๋ ์์น์ Qํด๋์ค๊ฐ ์ ์์ฑ๋ ๋ชจ์ต์ ๋ณผ ์ ์๋ค.
๊ธฐ์กด ์ฝ๋์ ๋ณ๊ฒฝ๋ ์ฝ๋ ๋น๊ต
@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์ ๋น๊ตํ๋ฉด ์๋์ ์ผ๋ก ๊ฐ๋ ์ฑ์ด ์ข๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
์ฃผ์ํ ์
์ ์ฉํ๋ค ์๊ธด ํธ๋ฌ๋ธ ์ํ ์ ๊ณต์ ํ๋ค.
-
์๋ํ๋ธ์ ํจ๊ป ์ฌ์ฉํ๋ค ๋ณด๋ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํต๊ณผํ์ง ๋ชปํด ๋น๋ํ ๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
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 } } }
-
distinct์ orderBy๊ฐ ๋์์ ์ ์ฉ๋์ง ์๋ ํ์์ด ๋ฐ์ํ๋ค.
์ฐพ์๋ณด๋ H2 ๋ฌธ๋ฒ ๋ฌธ์ ์๊ณ MySQL์์๋ ๊ฐ์ด ์ฌ์ฉํด๋ ์ ์ฉ์ด ๋์๋ค.
๊ทธ๋์ ์ผ๋จ์ workbook์ id๋ก groupBy๋ฅผ ํด์ distinct๋ฅผ ์ฌ์ฉํ์ง ์๋ ๋ฐฉํฅ์ผ๋ก ์์ ํ๋ค. ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ Flyway๋ฅผ ์ ์ฉํ ๋๋ ๊ทธ๋ ๊ณ ์ด๋ฒ์๋ ๊ทธ๋ ๊ณ local๊ณผ test์์๋ H2๋ฅผ ์ฌ์ฉํ๊ณ dev์ prod์์๋ Mariadb๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ H2์ MySQL์ ๋ฌธ๋ฒ ์ฐจ์ด ๋๋ฌธ์ ์ฌ๋ฌ ๋ฒ syntax ์๋ฌ๋ฅผ ๊ฒฝํํ๋ ์ผ์ด ๋ง์๋ค.
๊ทธ๋ฆฌํ์ฌ ๋ค์ ์คํ๋ฆฐํธ ๋๋ ์ด ์ฐจ์ด๋ฅผ ํด์์ํฌ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณผ ์๊ฐ์ด๋ค.
- ์ด๊ฑด ์ฐ๋ฆฌ๊ฐ ๊ฒช์ ํธ๋ฌ๋ธ ์ํ ์ ์๋์ง๋ง gradle์์ QueryDSL ์ค์ ๊ณผ ๊ด๋ จํด์ ๋ง์ด๋ค ์ด์๊ฐ ์๋ค๊ณ ํ๋ค. ํ์ฌ ์ฐ๋ฆฌ๊ฐ ์ ์ฉ์ํจ ์ค์ ๋ฐฉ๋ฒ์ ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํด์ Qํด๋์ค๋ฅผ ๋ง๋๋๋ฐ ์ด๋ gradle ๋ฒ์ ์ด ์ ๊ทธ๋ ์ด๋ ๋จ์ ๋ฐ๋ผ ์ฌ๋ฌ ๊ฐ์ง ์ค์ ์ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค. ๊ทธ๋์ ์ด ํ๋ฌ๊ทธ์ธ์ ๊ฑท์ด๋ด๊ธฐ ์ํด gradle AnnotationProcessor๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ด ์๋ค๊ณ ํ๋ค. ์ด๊ฒ ๊ถ๊ธํ๋ค๋ฉด http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ๋๋ก ํ์.