[๊ฐ์] ์ค์ ! Querydsl 1
https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84
์ค์ ! Querydsl - ์ธํ๋ฐ | ๊ฐ์
Querydsl์ ๊ธฐ์ด๋ถํฐ ์ค๋ฌด ํ์ฉ๊น์ง, ํ๋ฒ์ ํด๊ฒฐํด๋ณด์ธ์!, ๋ณต์กํ ์ฟผ๋ฆฌ, ๋์ ์ฟผ๋ฆฌ๋ ์ด์ ์๋ ! Querydsl๋ก ์๋ฐ ๋ฐฑ์๋ ๊ธฐ์ ์ ๋จ๋จํ๊ฒ. ๐ฉ ๋ณธ ๊ฐ์๋ ๋ก๋๋งต ๊ณผ์ ์ ๋๋ค. ๋ณธ ๊ฐ์๋ ์๋ฐ ๋ฐฑ์
www.inflearn.com
0. Querydsl ์๊ฐ
์ฟผ๋ฆฌ๋ฅผ ์๋ฐ ์ฝ๋๋ก ์์ฑํ๊ฒ ๋์์ฃผ์ด ๋ฌธ๋ฒ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ์์ ์ ์ก์ ์ ์์
๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์์
๋จ์ ๋ฐ๋ณต ์ฝ๋๊ฐ ์ค์ด๋ ๋ค
1. ํ๋ก์ ํธ ํ๊ฒฝ์ค์
ํญ์ ํ๋ ํ๊ฒฝ ์ค์ ์ ํด์ค๋ค
gradle๋ intelij๋ก
ํน์ ์๋๋ค๋ฉด enable annotation processing๋ ์ผ์ค๋ค
ํ ์คํธ ์ฉ์ผ๋ก ์ํฐํฐ ํ๋ ๋ง๋ค๊ณ ์ gradle์ ์๋ complieQuerydsl ๋ฐ๋ฅ!
ํน์ ํด๋น ํ๋ก์ ํธ์์ ./gradlew clean์ด๋ complieJava ๋ช ๋ น์ด ์ด์ฉํด์ ๋น๋ ๊ฐ๋ฅ
์์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋๋ค๋ฉด ์๋์ฒ๋ผ ํด๋ณด์
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
plugins {
id 'org.springframework.boot' version '2.6.0'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
//querydsl ์ถ๊ฐ
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
id 'java'
}
group = 'study'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
//querydsl ์ถ๊ฐ
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
//querydsl ์ถ๊ฐ ์์
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
compileQuerydsl{
options.annotationProcessorPath = configurations.querydsl
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}
//querydsl ์ถ๊ฐ ๋
๊ทธ๋ผ build ํด๋์ ์ด๋ฐ๊ฒ ์๊ธด๋ค.
์๋ ์๋์ผ๋ก generate ๋๋ ๊ฑฐ๋ผ git์ ์ฌ๋ฆฌ๋ฉด ์๋๋ค.(git ignore ํด์ผํจ, ๋ณดํต ๊ธฐ๋ณธ์ผ๋ก ๋์ด์๋ค)
์ฌ์ฉ์ ์ด๋ ๊ฒ!
@SpringBootTest
@Transactional
class QuerydslApplicationTests {
@Autowired
EntityManager em;
@Test
void contextLoads() {
Hello hello = new Hello();
em.persist(hello);
JPAQueryFactory query = new JPAQueryFactory(em);
QHello qHello = QHello.hello;
Hello result = query
.selectFrom(qHello)
.fetchOne();
Assertions.assertThat(result).isEqualTo(hello);
Assertions.assertThat(result.getId()).isEqualTo(hello.getId());
}
}
Querydsl - Unified Queries for Java
Unified Queries for Java. Querydsl is compact, safe and easy to learn. <!-- Querydsl Unified Queries for Java Querydsl provides a unified querying layer for multiple backends in Java. Compared to the alternatives Querydsl is more compact, safer and easier
querydsl.com
์๋ ๋ ์ค ํ๋๋ฅผ ์ ํํ๋ฉด ์ฟผ๋ฆฌ์ ? ๊ฐ์ด ๋์จ๋ค (์ฑ๋ฅ ์ ํ๋ ์ ์์ผ๋ ๊ด์ฐฎ์์ง ๋ณด๊ณ ์ฌ์ฉ)
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.8'
org.hibernate.type: trace
2. ์์ ๋๋ฉ์ธ ๋ชจ๋ธ
๋ง์ถฐ์ ์ํฐํฐ ๋ง๋ค๊ณ ํ ์คํธ ๊ตฟ
@SpringBootTest
@Transactional
//@Commit ์์ผ๋ฉด ๋กค๋ฐฑ์ ํด๋ฒ๋ฆผ ํ์ง๋ง ๋ค๋ฅธ ํ
์คํธ์ ๊ผฌ์ผ ์ ์์
public class MemberTest {
@PersistenceContext
EntityManager em;
@Test
public void testEntity() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
//์ด๊ธฐํ
em.flush();
em.clear();
//ํ์ธ
List<Member> members = em.createQuery("select m from Member m",
Member.class)
.getResultList();
for (Member member : members) {
System.out.println("member=" + member);
System.out.println("-> member.team=" + member.getTeam());
}
}
}
3. ๊ธฐ๋ณธ ๋ฌธ๋ฒ
- JPQL vs Querydsl
@Test
public void startJPQL() {
String qlString =
"select m from Member m " +
"where m.username = :username";
Member findMember = em.createQuery(qlString, Member.class)
.setParameter("username", "member1")
.getSingleResult();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
public void startQuerydsl() {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QMember m = new QMember("m");
Member findMember = queryFactory
.select(m)
.from(m)
.where(m.username.eq("member1"))//ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ ์ฒ๋ฆฌ
.fetchOne();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
JPQL์ ์ฟผ๋ฆฌ๊ฐ ํ๋ฆฌ๋ฉด ์คํ ํด์ผ ์๋ ค์ค
Querydls์ ์ปดํ์ผ ์์ ์ ์ค๋ฅ๋ฅผ ์ก์์ค๋ค.
package study.querydsl;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import study.querydsl.entity.Member;
import study.querydsl.entity.QMember;
import study.querydsl.entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
@SpringBootTest
@Transactional
public class QuerydslBasicTest {
@PersistenceContext
EntityManager em;
JPAQueryFactory queryFactory;
@BeforeEach
public void before() {
//๋ฉํฐ์ค๋ ๋์ ๋ฌธ์ ์๊ฒ em์์ ์ ๋์ด์์ด์ ๊ด์ฐฎ์
//๋์์ฑ ๋ฌธ์ ์์๊น
queryFactory = new JPAQueryFactory(em);
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
}
@Test
public void startJPQL() {
String qlString =
"select m from Member m " +
"where m.username = :username";
Member findMember = em.createQuery(qlString, Member.class)
.setParameter("username", "member1")
.getSingleResult();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
public void startQuerydsl() {
QMember m = new QMember("m");
Member findMember = queryFactory
.select(m)
.from(m)
.where(m.username.eq("member1"))//ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ ์ฒ๋ฆฌ
.fetchOne();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
}
- ๊ธฐ๋ณธ Q-Type ํ์ฉ
@Test
public void startQuerydsl() {
Member findMember = queryFactory
.select(member)
.from(member)
.where(member.username.eq("member1"))//ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ ์ฒ๋ฆฌ
.fetchOne();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
์ฝ๋์ ๋ฆฌ
QMember์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ์ธ๋์ด์๋ member ์ฌ์ฉ(static import)
- ๊ฒ์ ์กฐ๊ฑด ์ฟผ๋ฆฌ
@Test
public void startQuerydsl() {
Member findMember = queryFactory
.select(member)
.from(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.fetchOne();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
and๋ ์ด๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ค
@Test
public void startQuerydsl() {
Member findMember = queryFactory
.select(member)
.from(member)
.where(
member.username.eq("member1")
,member.age.eq(10)
)
.fetchOne();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
์จ๊ฐ ์กฐ๊ฑด๋ค
member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() //์ด๋ฆ์ด is not null
member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //between 10, 30
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
member.username.like("member%") //like ๊ฒ์
member.username.contains("member") // like ‘%member%’ ๊ฒ์
member.username.startsWith("member") //like ‘member%’ ๊ฒ์
- ๊ฒฐ๊ณผ ์กฐํ
fetch() : ๋ฆฌ์คํธ ์กฐํ, ๋ฐ์ดํฐ ์์ผ๋ฉด ๋น ๋ฆฌ์คํธ ๋ฐํ
fetchOne() : ๋จ ๊ฑด ์กฐํ
๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด : null
๊ฒฐ๊ณผ๊ฐ ๋ ์ด์์ด๋ฉด : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResults() : ํ์ด์ง ์ ๋ณด ํฌํจ, total count ์ฟผ๋ฆฌ ์ถ๊ฐ ์คํ
fetchCount() : count ์ฟผ๋ฆฌ๋ก ๋ณ๊ฒฝํด์ count ์ ์กฐํ
fetchOne์ ์ฌ๋ฌ๊ฐ๋ผ ์ค๋ฅ๋จ ์๋ต
- ์ ๋ ฌ
@Test
public void sort() {
em.persist(new Member(null, 100));
em.persist(new Member("member5", 100));
em.persist(new Member("member6", 100));
//๋์ด ๋ด๋ฆผ์ฐจ, ์ด๋ฆ ์ค๋ฆ์ฐจ ์
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(100))
.orderBy(member.age.desc(), member.username.asc().nullsLast())
.fetch();
Member member5 = result.get(0);
Member member6 = result.get(1);
Member memberNull = result.get(2);
//์ด๋ฆ์ด null์ธ ์ฌ๋์ด ๊ฐ์ฅ ๋ง์ง๋ง์ ๋์์ผ ํ๋ค (.nullsLast())
//๋ฐ๋๋ nullFirst๋ ์์
assertThat(member5.getUsername()).isEqualTo("member5");
assertThat(member6.getUsername()).isEqualTo("member6");
assertThat(memberNull.getUsername()).isNull();
}
- ํ์ด์ง
@Test
public void paging1() {
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1) //0๋ถํฐ ์์(zero index)
.limit(2) //์ต๋ 2๊ฑด ์กฐํ
.fetch();
assertThat(result.size()).isEqualTo(2);
}
@Test
public void paging2() {
QueryResults<Member> queryResults = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetchResults();
assertThat(queryResults.getTotal()).isEqualTo(4);
assertThat(queryResults.getLimit()).isEqualTo(2);
assertThat(queryResults.getOffset()).isEqualTo(1);
assertThat(queryResults.getResults().size()).isEqualTo(2);
}
- ์งํฉ
@Test
public void aggregation() throws Exception {
List<Tuple> result = queryFactory
.select(member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min())
.from(member)
.fetch();
Tuple tuple = result.get(0);
assertThat(tuple.get(member.count())).isEqualTo(4);
assertThat(tuple.get(member.age.sum())).isEqualTo(100);
assertThat(tuple.get(member.age.avg())).isEqualTo(25);
assertThat(tuple.get(member.age.max())).isEqualTo(40);
assertThat(tuple.get(member.age.min())).isEqualTo(10);
}
@Test
public void group() throws Exception {
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team)
.groupBy(team.name)
.fetch();
Tuple teamA = result.get(0);
Tuple teamB = result.get(1);
assertThat(teamA.get(team.name)).isEqualTo("teamA");
assertThat(teamA.get(member.age.avg())).isEqualTo(15);
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}
sql๊ณผ ๋์ผํ๊ฒ ์ฌ์ฉํ ์ ์๋ค having๋ ์ฌ์ฉ ๊ฐ๋ฅ
- ์กฐ์ธ - ๊ธฐ๋ณธ์กฐ์ธ
join() , innerJoin() : ๋ด๋ถ ์กฐ์ธ(inner join)
leftJoin() : left ์ธ๋ถ ์กฐ์ธ(left outer join)
rightJoin() : rigth ์ธ๋ถ ์กฐ์ธ(rigth outer join)
@Test
public void join() throws Exception {
QMember member = QMember.member;
QTeam team = QTeam.team;
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
thetaJoin : ์ฐ๊ด ๊ด๊ณ๊ฐ ์๋ ์กฐ์ธ
from์ ์ฌ๋ฌ ์ํฐํฐ๋ฅผ ์ ํํ์ฌ ๋ ๋ค ๊ณฑํ๊ธฐ
์ธ๋ถ ์กฐ์ธ ๋ถ๊ฐ๋ฅ, ์กฐ์ธ on์ ์ ์ฌ์ฉํ๋ฉด ์ธ๋ถ ์กฐ์ธ๋ ๊ฐ๋ฅ
- ์กฐ์ธ - on ์
ON์ ์ ํ์ฉํ ์กฐ์ธ(JPA 2.1๋ถํฐ ์ง์)
1. ์กฐ์ธ ๋์ ํํฐ๋ง
2. ์ฐ๊ด๊ด๊ณ ์๋ ์ํฐํฐ ์ธ๋ถ ์กฐ์ธ
์์
* JPQL: SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'teamA'
* SQL: SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='teamA'
@Test
public void join_on_filtering() throws Exception {
//select๊ฐ ์ฌ๋ฌ๊ฐ์ง ํ์
์ด๋ผ ํํ์
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
๋ด๋ถ ์กฐ์ธ์ผ ๋๋ on๊ณผ where์ ๊ธฐ๋ฅ์ด ๋๊ฐ์
๋ ์ต์ํ where์ ์ฃผ๋ก ์ฌ์ฉํ๋๋ก!
* JPQL: SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
* SQL: SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
@Test
public void join_on_no_relation() throws Exception {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
for (Tuple tuple : result) {
System.out.println("t=" + tuple);
}
}
leftjoin์ team ํ๋๋ง ๋ค์ด๊ฐ๋ ๊ฒ์ด ํน์ง
์ด์ฐจํผ ๋น๊ตํด์ ๋ถ์ด๋ ๊ฒ ์๋๋ผ ๋ ๊ฐ ๋ฃ์ ํ์๊ฐ ์์
- ์กฐ์ธ - ํ์น์กฐ์ธ
์ฑ๋ฅ ์ต์ ํ์ ์ฃผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() throws Exception {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team)
.fetchJoin()//์ด๊ฑธ ๋ถ์ด๋ฉด fetch๊ฐ ๋๋ค
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded =
emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());//ํ์น ๋๋์ง ์๋๋์ง ์ฌ๋ถ
}
- ์๋ธ ์ฟผ๋ฆฌ
@Test
public void subQuery() throws Exception {
//๊ฒน์น๋ฉด ์๋๋ ๊ฒฝ์ฐ์๋ ์๋ก ์ ์ธ(ex. ์๋ธ์ฟผ๋ฆฌ์ฉ)
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(40);
}
eq๋ฟ ์๋๋ผ goe, in๋ ๋๊ฐ์ด ๊ฐ๋ฅํ๋ค
select ์ ์ ์๋ธ์ฟผ๋ฆฌ(JPAExpressions static import)
@Test
public void select_subQuery() throws Exception {
QMember memberSub = new QMember("memberSub");
List<Tuple> fetch = queryFactory
.select(member.username,
select(memberSub.age.avg())
.from(memberSub)
).from(member)
.fetch();
for (Tuple tuple : fetch) {
System.out.println("username = " + tuple.get(member.username));
System.out.println("age = " +
tuple.get(select(memberSub.age.avg())
.from(memberSub)));
}
}
from ์ ์ ์๋ธ์ฟผ๋ฆฌ๋ ์๋๋ค.
-> ํด๊ฒฐ
1. join์ผ๋ก ๋ณ๊ฒฝ
2. ์ฟผ๋ฆฌ2๋ฒ ๋ถ๋ฆฌ
3. native SQL ์ฌ์ฉ(์ด๊ฑด JPA์ ํ๊ณ)
ํ ๋ฐฉ ์ฟผ๋ฆฌ๋ณด๋ค๋ ์ฌ๋ฌ ์ฟผ๋ฆฌ๋ก ๋๋ ์ ํธ์ถํ๋ ๊ฒ์ด ํจ์ฌ ํจ์จ์ ์ผ ๋๊ฐ ์๋ค
- case๋ฌธ
ํ ์๋ ์์ง๋ง ์ ๋งํ๋ฉด DB์์ ์ฒ๋ฆฌํ์ง ๋ง์
@Test
public void basicCase(){
List<String> result = queryFactory
.select(member.age
.when(10).then("์ด์ด")
.when(20).then("์ค๋ฌด์ด")
.otherwise("๊ธฐํ"))
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
public void complexCase(){
List<String> result = queryFactory
.select(new CaseBuilder()
.when(member.age.between(0, 20)).then("0~20์ด")
.when(member.age.between(21, 30)).then("21~30์ด")
.otherwise("๊ธฐํ"))
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
- ์์, ๋ฌธ์ ๋ํ๊ธฐ
//๋
๋ค ์์ ๋ถ์ด๊ธฐ
@Test
public void constant(){
Tuple result = queryFactory
.select(member.username, Expressions.constant("A"))
.from(member)
.fetchFirst();
}
@Test
public void concat(){
//.stringValue()๋ก ์ซ์ -> ๋ฌธ์ ์ ํ
String result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue())) //{username_age}
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
}