- Domain-Specific Language. ๋๋ฉ์ธ ํนํ ์ธ์ด.
- ๊ฐ์ฅ ํฐ ์์ ๊ฐ๋ .
- Java๋ Python ๊ฐ์ ๋ฒ์ฉ ์ธ์ด์ ๋ฌ๋ฆฌ ํน์ ๋ชฉ์ ์๋ง ์ฌ์ฉ๋๋๋ก ์ค๊ณ๋ ์ธ์ด์ด๋ค.
- ๋์ ๊ฐ๋ ์ฑ๊ณผ ๊ฐ๊ฒฐํจ์ ๊ฐ๊ฒฐํจ์ ๊ฐ์ง๋ค.
- ์๋ 2๊ฐ์ง์ ์ข
๋ฅ๊ฐ ์๋ค.
- External DSL: ๋ณ๋์ ๋ฌธ๋ฒ์ ๊ฐ์ง ์์ ํ ๋ ๋ฆฝ ์ธ์ด (์: SQL, HTML, CSS, ์ ๊ทํํ์ ๋ฑ๋ฑ)
- Internal DSL: Java๋ Kotlin ๊ฐ์ ๊ธฐ์กด ์ธ์ด์ ๋ฌธ๋ฒ ๋ด์์, ํน์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ API๋ฅผ ํตํด DSL์ฒ๋ผ ๋ณด์ด๊ฒ ๋ง๋ ๊ฒ (์: QueryDSL, Gradle Groovy ๋ฑ๋ฑ)
- Internal DSL์ ๊ตฌํํ๋ ๋ฐ ์์ฃผ ์ฌ์ฉ๋๋ ๋์์ธ ํจํด.
- ๋ฉ์๋ ์ฒด์ด๋์ ์ฌ์ฉํ๋ค.
- ๋๋ถ๋ถ์ ๋ฉ์๋๊ฐ ์๊ธฐ ์์ (this)์ด๋ ๋ค์ ๋จ๊ณ์์ ์ฌ์ฉํ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ๋ค.
- ์ด๋ฅผ ํตํด ์ฌ๋ฌ ๋ฉ์๋๋ฅผ . ์ผ๋ก ์ฐ๊ฒฐํ์ฌ ๋ง์น ํ๋์ ๋ฌธ์ฅ์ฒ๋ผ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
- ๋์ผํ ๋ด์ฉ์ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ณ ์ฝ๊ธฐ ์ฝ๊ฒ ์์ฑํ ์ ์๋ค.
- QueryDSL์ ์ ๋ ๊ฐ์ง ๊ฐ๋ ์ ํฉ์น ํน์ ์๋ฐ/์ฝํ๋ฆฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ.
- Java ์ฝ๋ ๊ธฐ๋ฐ์ผ๋ก ํ์ ์ ์์ ํ(type-safe) ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๊ฒ ํด์ฃผ๋ Internal DSL์ด๋ค.
- ์ฆ, ๊ธฐ๋ณธ์ ์ธ ๋ฌธ๋ฒ์ ๋ชจ๋ JPQL์ ๋ฐ๋ผ๊ฐ๋ค.
- QueryDSL์ ์์ ํ ์๋ก์ด ์ฟผ๋ฆฌ ์ธ์ด๊ฐ ์๋๋ผ ๋ฌธ์์ด๋ก ์ง์ ์์ฑํ๋ JPQL ์ฟผ๋ฆฌ๋ฌธ์ Java ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ์ด์ฉํด ๋์ ์กฐ๋ฆฝํด์ฃผ๋ ๋๊ตฌ์ด๋ค!
- Fluent API ํจํด(๋ฉ์๋ ์ฒด์ด๋)์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์ถํ๋ค.
- QueryDSL์ Q-Type์ด๋ผ๋ ์ปดํ์ผ ์์ ์ ์์ฑ๋๋ ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ, ์๋ฐ ์ฝ๋(๊ฐ์ฒด์ ๋ฉ์๋)๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
- ๋ง์ฝ ์ฟผ๋ฆฌ ๋ด์์ ์กด์ฌํ์ง ์๋ ๊ฐ์ฒด๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ฆ์ ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์ฝ๋๋ฅผ ์์ฑํ ๋ IED๊ฐ ๋ค์์ ์ ๋ ฅํ ์ ์๋ ์ฝ๋ ๋ชฉ๋ก์ ์๋์ผ๋ก ๋ณด์ฌ์ค๋ค.
์ฟผ๋ฆฌ ๋ฌธ๋ฒ์ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ์์ ์ ์ก์๋ด์ค๋ค. ๋ฌธ์์ด ์ฟผ๋ฆฌ๋ฌธ(JPQL)์ ๊ฒฝ์ฐ ์ปดํ์ผ ์์ ์ ๋ฌธ๋ฒ ์ค๋ฅ๋ฅผ ์ก์ง ๋ชปํ๊ณ ๋ฐํ์์ด ๋์ด ํด๋น ์ฟผ๋ฆฌ๊ฐ DB์ ์ ์กํ ๋๊ฐ ๋์ด์์ผ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
๋๋ฉ๋ ๊ฐ์ฒด ํ๋๋ช ์ ์ค๋ฅ๋ ๋ฐ์ดํฐ ํ์ ์ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ์์ ์์ ์ก์๋ผ ์ ์๋ค.
์ฝ๋์ ๊ตฌ์กฐ๋ฅผ ๋ณ๊ฒฝํ ๋ ์ฟผ๋ฆฌ ์ฝ๋๊น์ง ํจ๊ป ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋ค. IDE์ ๋ฆฌํํ ๋ฆฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํด ํ๋๋ช ์ ๋ฐ๊พธ๋ฉด ์ฟผ๋ฆฌ ์ฝ๋์ ํ๋๋ช ์ญ์ ํจ๊ป ๋ณ๊ฒฝ๋๋ค.
์ฆ, ๋ฐํ์์์๋ ์ก์ ์ ์์๋ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ์์ ์์ ์ก์ ์ ์๊ฒ ๋์๋ค!
๋์ ์ฟผ๋ฆฌ๋ ๋ฐํ์์์ ์ ๋ ฅ์ ๋ฐ๋ผ WHERE / JOIN / ORDER BY ๋ฑ์ ์กฐ๊ฑด์ ๋์ ์ผ๋ก ๊ตฌ์ฑํด SQL์ ์์ฑํ๊ณ ์คํํ ์ ์๋ค. ๋จ, ์ฌ์ฉ์์ ์ ๋ ฅ์ ๊ทธ๋๋ก ๋ฌธ์์ด์ ๊ฒฐํฉํ ์ SQL ์ธ์ ์ ์ ๋นํ ์ ์๋ค.
Qํด๋์ค๋ QueryDSL์์ ์ํฐํฐ(Entity)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ฑ๋๋ ์ฟผ๋ฆฌ ์ ์ฉ ํด๋์ค์ด๋ค. Qํด๋์ค๋ฅผ ์ฌ์ฉํ ๋์ ์ด์ ์ ์๋์ ๊ฐ๋ค.
- ํ์ ์์ ์ฑ : ์ปดํ์ผ ๋จ๊ณ์์ ์ํฐํฐ ๊ธฐ๋ฐ์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํจ์ผ๋ก ์๋ชป๋ ํ๋๋ ํ์ ์ ์ฌ์ฉํ ์ ์๋ค. ๋ฐํ์ ์ ์ค๋ฅ๋ฅผ ๋ฐ์์ํค์ง ์๋๋ค.
- ์๋ ์์ฑ : IDE๊ฐ ์๋์์ฑ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
- Fluent Interface ์คํ์ผ : ๋ฉ์๋ ์ฒด์ด๋์ ์ฌ์ฉํ์ฌ ์ฝ๊ธฐ ์ฌ์ด ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ค.
- Internal DSL ํจ๊ณผ : ์๋ฐ์ ์ฝ๋ ์์์ ๋๋ฉ์ธ ํนํ ์ธ์ด์ฒ๋ผ ํํํ ์ ์๋ค. ๊ทธ๋ฌ๋ Qํด๋์ค๋ฅผ ๊ทธ๋๋ก ๊นํ๋ธ์ ์ฌ๋ ค์๋ ์ ๋๋ค!!
- Qํด๋์ค๋ ๊ฐ์ฒด ๊ตฌ์กฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์๋ฐ ๋ฒ์ ๋ง๋ค ๋ค๋ฅด๊ฒ ์์ฑ๋๋ค.
- ๋ฐ๋ผ์ ํ์๋ง๋ค Qํด๋์ค๋ฅผ ๊ณต์ ํ ์ ์๋ค!
- ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๊นํ๋ธ์ ๋ฌด์ํ ํ์ผ์ ์ค์ ํ๋ .gitignore ํ์ผ์ Qํด๋์ค๊ฐ ์์ฑ๋๋ ํด๋(์: build/generated/ ๋๋ target/generated-sources/)๋ฅผ ์ถ๊ฐํ๋ค.
- ์์ ์ค๋ช ํ๋ฏ์ด QueryDSL๋ JPQL์ ๋ฌธ๋ฒ์ ๋ฐ๋ฅธ๋ค!
- ํํฐ๋ง์ ์ํ join ๋ฉ์๋์ ์ค์ ํ ์ด๋ธ์ ๊ฒฐํฉํ๊ธฐ ์ํ fetch join์ด ๋ณ๊ฐ๋ก ์กด์ฌํ๋ค.
- ์ด ์ค fetch join๋ฅผ ์ฌ์ฉํด์ผ N+1 ๋ฌธ์ ์์ด ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
- DTO ์์ฑ์ @QueryProjection ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํ๋ค.
- ์ด ๋ฐฉ์์ Projections.constructor์ ๋ฌ๋ฆฌ ์ปดํ์ผ ์์ ์ DTO ์์ฑ์์ ํ์ ์ด๋ ์ธ์ ๊ฐ์๊ฐ ์๋ชป๋๋ฉด ์ปดํ์ผ ์ค๋ฅ๋ฅผ ๋ฐ์์์ผ์ ์์ ํ๋ค.
- @QueryProjection ์ฌ์ฉ ํ Gradle/Maven์ผ๋ก ์ปดํ์ผํ๋ฉด QMemberDto ํด๋์ค๊ฐ ์๋ ์์ฑ๋๋ค.
- @QueryProjection๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ ํ DTO๊ฐ ๋ค๋ฅธ DTO๋ฅผ ํ๋๋ก ๊ฐ์ง๋ ๊ตฌ์กฐ(DTO์์ DTO)๋ฅผ ์ฟผ๋ฆฌ ํ ๋ฒ์ผ๋ก ์ฝ๊ฒ ๋งคํํ ์ ์๋ค.
// QMember๊ฐ QTeamDto๋ฅผ ํ๋๋ก ๊ฐ์ง ๋
queryFactory.select(
new QMemberDTO(
member.name,
new QTeamDto(team.name, team.location)))- JPA์ Pageable ๊ฐ์ฒด๋ฅผ ์ง์ ์ฌ์ฉํ๊ธฐ ์ด๋ ต๊ฑฐ๋ ๋ณต์กํ join์ด ํ์ํ ๋ Pageable ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์๋์ผ๋ก ํ์ด์ง๋ค์ด์ ์ ๊ตฌํํ๋ ๋ฐฉ์์ด๋ค.
- ์๋ 2๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ํ์ํ๋ค.
- ๋ด์ฉ ์กฐํ ์ฟผ๋ฆฌ: offset()๊ณผ limit()์ ์ฌ์ฉ
- ์ ์ฒด ๊ฐ์ ์กฐํ ์ฟผ๋ฆฌ: select(member.count())๋ฅผ ์ฌ์ฉ
// Predicate ๊ฐ์ฒด, Pageable ๊ฐ์ฒด๊ฐ ํ๋ผ๋ฏธํฐ๋ก ๋์ด์๋ค๊ณ ๊ฐ์
Pageable pageable = PageRequest.of(0, 10); // 0๋ฒ ํ์ด์ง, 10๊ฐ์ฉ
// 1. ๋ด์ฉ ์กฐํ ์ฟผ๋ฆฌ
List<Member> content = queryFactory
.selectFrom(member)
.orderBy(member.name.desc())
.offset(pageable.getOffset()) // ํ์ด์ง ๊ณผ์ ์ ์๋์ผ๋ก ์์ฑ
.limit(pageable.getPageSize())
.fetch();
// 2. ์ ์ฒด ๊ฐ์ ์กฐํ ์ฟผ๋ฆฌ
Long total = queryFactory
.select(member.count())
.from(member)
.where(predicate) // content ์ฟผ๋ฆฌ์ where ์กฐ๊ฑด๊ณผ ๋์ผํด์ผ ํจ
.fetchOne();
// 3. PageImpl ๊ฐ์ฒด๋ก ์กฐํฉํ์ฌ ๋ฐํ
return new PageImpl<>(content, pageable, total);- ์ํ๋ ์ฟผ๋ฆฌ๋ฌธ์ ํ์ด์ง ์์คํ ์ ํ ๋ฒ ๋ ๋ง์์ฐ๋ ๋๋์ผ๋ก ์๊ฐํ๋ฉด ๋๋ค.
- PageImpl ๊ฐ์ฒด๋ ํ์ด์ง์ ํตํด DB์์ ์๋ผ ์จ 10๊ฐ์ ๋ฐ์ดํฐ(content)์ ์ ์ฒด ๊ฐ์๊ฐ 100๋ง ๋ช ๊ฐ์ธ์ง๋ฅผ ์๋ ค์ฃผ๋ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ(count)๋ฅผ ํฉ์ณ์ ํฌ์ฅํด์ฃผ๋ ์ญํ ์ ํ๋ค.
- ์ด๋ฅผ ํตํด List๋ง์ผ๋ก๋ ์ ์ ์๋ ํ์ด์ง์ ๊ดํ ์ ๋ณด๋ฅผ ํ๋ก ํธ์๊ฒ ๋๊ธธ ์ ์๊ฒ ๋๋ค.
- ์กฐํ ๊ฒฐ๊ณผ๋ฅผ Map ๊ฐ์ ์ปฌ๋ ์ ์ผ๋ก ๋ณํํ ๋, ํนํ 1:N ๊ด๊ณ๋ฅผ DTO๋ก ๋งคํํ ๋ ์ ์ฉํ ๊ธฐ๋ฅ์ด๋ค
- ์ผ๋ฐ์ ์ธ JPQL์ SELECT ์ ์์ ์ปฌ๋ ์ ์ ๋ฐํํ ์ ์๋ค.
- ํ์ค JPQL์ SELECT ์ ์ ์ํฐํฐ๋ DTO ๊ฐ์ ๋จ์ผ ๊ฐ์ฒด๋ง ๋ฐํํ ์ ์๋ค.
SELECT new TeamDto(..., List<MemberDto>)์ฒ๋ผ DTO ์์ฑ์์ ์ปฌ๋ ์ (List)์ ์ง์ ๋ฃ์ ์ ์๋ค๋ ๋ป์ด๋ค.- ๊ทธ๋ฌ๋ QueryDSL์ transform์ผ๋ก ์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํด์ค๋ค.
- transform์ map์ผ๋ก ๋ฐํ๋ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ผ๋จ ๋ฉ๋ชจ๋ฆฌ๋ก ๊ฐ์ ธ์จ๋ค. ์ดํ QueryDSL์ด ์ด ๋ฐ์ดํฐ๋ฅผ GroupBy.groupBy(...).as(...)์ ๋ง์ถฐ ์ํ๋ ๊ตฌ์กฐ๋ก ์ฌ์กฐ๋ฆฝํด์ค๋ค.
- ๋จ, transform์ DB์์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ผ๋จ ๊ฐ์ ธ์์ ์ ํ๋ฆฌ์ผ์ด์ ๋ฉ๋ชจ๋ฆฌ์์ ๊ทธ๋ฃนํํ๋ฏ๋ก ๋ฐ์ดํฐ๊ฐ ์์ญ๋ง ๊ฑด ์ด์์ผ๋ก ๋งค์ฐ ๋ง์ ๊ฒฝ์ฐ ์ฑ๋ฅ ์ ํ์ ์์ธ์ด ๋ ์ ์๋ค.
- ์ ๋ ฌ ๋ฉ์๋๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ ฌํ๋ ค ํ ๋ ํด๋น ์ ๋ ฌ ๊ธฐ์ค์ด NULL์ธ ๊ฐ๋ค์ ๋ชจ๋ ๊ฐ์ ์ ์ผ ์์ ๋์ง, ์ ์ผ ๋ค์ ์ค ์ง๋ฅผ ์ง์ ํ๋ ๊ธฐ๋ฅ์ด๋ค.
- ๋งจ ์์์ด๋ผ๋ฉด nullsFirst(), ๋งจ ๋ค๋ผ๋ฉด nullsLast()๋ฅผ ์ฌ์ฉํ๋ค.
- DB๋ง๋ค NULL์ ๊ธฐ๋ณธ ์ ๋ ฌ ์์๊ฐ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์(ex. MySQL/H2๋ ๋งจ ์, Oracle์ ๋งจ ๋ค) ์ผ๊ด๋ ์ ๋ ฌ์ ์ํด ์ฌ์ฉํ๊ฒ ๋๋ค.
- ๋ด๊ฐ ์์ฑํ ๋ฆฌ๋ทฐ ๋ณด๊ธฐ API๋ฅผ QueryDSL๋ก ๊ตฌํํ๊ธฐ
- ํํฐ๋ง ์กฐ๊ฑด : ๊ฐ๊ฒ๋ณ, ๋ณ์ ๋ณ
- ์ ์ฝ ์กฐ๊ฑด : ํ๋์ API๋ก ์ค๊ณํ ๊ฒ
public interface reviewRepositoryQueryDsl {
List<ShopReview> searchShopReview(Predicate predicate);
}- QueryDSL๋ก ๊ตฌํํ๊ณ ์ถ์ ๋ฉ์๋๋ค์ ์ธํฐํ์ด์ค ์์์ ๋ฏธ๋ฆฌ ์ ์ํ๋ค.
- ์ธํฐํ์ด์ค์ ์ ์ํ๋ ๋ชจ๋ ๋ฉ์๋๋ ์๋์ผ๋ก public์ด๊ณ abstract๊ฐ ๋๋ค.
- ๋ฐ๋ผ์ public์ด๊ณ abstract๋ฑ์ ํค์๋ ์์ฑ์ ํ์ํ์ง ์๋ค.
- ์ธ์๋ก ๋ฐ๊ฒ ๋๋ Predicate ํ์ ์ ๊ฒฝ์ฐ queryDSL์์ ์ ๊ณตํ๋ ๊ฒ์ธ์ง ์๋์ง ์ ๋ณด๊ณ importํ์ฌ์ผ ํ๋ค.
@Repository
@RequiredArgsConstructor
public class reviewRepositoryQueryDslImpl implements reviewRepositoryQueryDsl {
private final EntityManager em;
@Override
public List<ShopReview> searchShopReview(Predicate predicate){
// JPA ์ธํ
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
// Qํด๋์ค ์ ์ธ
QShopReview review = QShopReview.shopReview;
QShop shop = QShop.shop;
QMember member = QMember.member;
return queryFactory
.selectFrom(review)
.join(review.shop, shop).fetchJoin()
.join(review.member, member)
.where(predicate)
.fetch();
}
}- ํ์ฌ ๋ณ์ ์ ShopReview ์ํฐํฐ ์์ ์กด์ฌ โ Qํด๋์ค๋ฅผ ํตํ join์ด ํ์ํ์ง ์๋ค.
- join์ด ํ์ํ shop๊ณผ member์ ๊ฒฝ์ฐ Qํด๋์ค๋ฅผ ํตํด joinํ๋ค.
- ์ด๋ join์ ํํฐ๋ง(where์ )์ ์ํ ๊ฒ์ด๊ณ fetchJoin์ ์ฌ์ฉํด์ผ N+1 ๋ฌธ์ ์์ด ํ ์ด๋ธ์ ๊ฒฐํฉํด์ ๊ฐ์ ธ์จ๋ค.
- ๋ชจ๋ ๊ฒ์์ ์กฐ๊ฑด์ Service์์ ์ ๋ฌ๋ฐ์ Predicate๋ฅผ ํตํด ๊ฒฐ์ ๋๋ค.
- ์ด๋ค member์ ๋ฆฌ๋ทฐ์ธ์ง, ์ด๋ค ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ์ธ์ง, ์ด๋ค ๋ณ์ ์ ๊ฐ์ง ๋ฆฌ๋ทฐ์ธ์ง๋ ์๋น์ค์์ ๊ฒฐ์ ๋๋ ๊ฒ.
public interface reviewRepository extends JpaRepository<ShopReview, Long>, reviewRepositoryQueryDsl {...}- ๋ฉ์ธ ์ธํฐํ์ด์ค์ธ reviewRepository๊ฐ JpaRepository์ ์ง์ ๋ง๋ reviewRepositoryQueryDsl๋ฅผ ๋ ๋ค ์์
- ์๋น์ค ๊ณ์ธต์์๋ ReviewRepository ํ๋๋ง ์ฃผ์ ๋ฐ์ผ๋ฉด reviewRepository.save() (JPA ๊ธฐ๋ณธ ๊ธฐ๋ฅ)์ reviewRepository.searchShopReview() (๋ด๊ฐ ๋ง๋ QueryDSL ๊ธฐ๋ฅ)๋ฅผ ๋ชจ๋ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.