@Test
public void startJPQL() {
Member findByJPQL = em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
assertThat(findByJPQL.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();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
fetchOne을 통해서 결과를 가지고 오는 부분말고는 큰 차이가 없다.
jpql이 제공하는 모든 검색조건
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%’ 검색
@Test
public void search() {
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1").and(member.age.eq(10)))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne() : 단 건 조회
결과가 없으면 : null
결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount() : count 쿼리로 변경해서 count 수 조
List<Member> fetch = queryFactory
.selectFrom(member)
.fetch();
Member member = queryFactory
.selectFrom(QMember.member)
.fetchFirst();
QueryResults<Member> memberQueryResults = queryFactory
.selectFrom(QMember.member)
.fetchResults();
fetchResults의 경우 취소선이 생기는것으로 볼때 더이상 권장되지 않는듯하다.
@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);
assertThat(member5.getUsername()).isEqualTo("member5");
assertThat(member6.getUsername()).isEqualTo("member6");
assertThat(memberNull.getUsername()).isNull();
}
조건
1. 나이순 정렬
2. 이름순 정렬
3. 이름이 없다면 마지막에 출력
orderBy를 통해 조건에 맞는 정렬을 한다.
@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);
}
JPQL과 똑같이 offset, limit를 걸어서 찾아온다.
전체 조회가 필요한 경우
fetchResult를 사용한다.
@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);
}
다만 count 쿼리가 실행되기때문에 성능저하 이슈가 있다.
집합은 Tuple형으로 조회하게 된다.
이는 aggregation함수 사용 예시이다.
@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);
}
이는 Group By 예시이다.
@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);
}
@Test
public void join() throws Exception {
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
이렇게 조인을 해서보내게 되면
select
member0_.member_id as member_i1_1_,
member0_.age as age2_1_,
member0_.team_id as team_id4_1_,
member0_.username as username3_1_
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.team_id
where
team1_.name=?
이렇게 inner join이 기본적으로 나가게 된다.
이를 left 조인이나 right 조인으로 바꿀 수도 있다.
세타조인은 연관관계가 없는 경우에 사용하는 조인이다.
@Test
public void theta_join() throws Exception {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Member> result = queryFactory
.select(member)
.from(member, team)
.where(member.username.eq(team.name))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("teamA", "teamB");
}
모든 데이터를 조인하고, 조건에 맞는 데이터를 가지고오는 방식이다.
회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원 전체 조회를 만드는 방법이다.
JPQL의 경우
JPQL: SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'teamA'
같이 만든다.
@Test
public void join_on_filtering() throws Exception {
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);
}
}
left조인으로 전체조회를 한 후, 그중에서 teamA만 eq를 통해 찾는방식이다.
혹은 innerjoin을 하면 on절을 쓰지 않고 where 절로 처리할 수 있다.
@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);
}
}
team과 left조인을 할때, 보통은 leftjoin(member.team, team)으로 member의 team과 조인을 하지만, 이를 빼버리면 그냥 team과 조인하게 된다.
이렇게 작성하면 member의 팀이 있는 경우의 데이터만 넘어오게된다.
패치조인이 적용되지 않은 상태면 프록시문제로 인해 로딩이 되지 않는다.
패치조인을 하려면, 기존 join문법과 동일하지만 뒤에 fetchJoin함수를 붙여주면 된다.
@Test
public void fetchJoinUse() throws Exception {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded =
emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 적용").isTrue();
}
}
이렇게 적용하면 한번에 다 가지고온다 .
쿼리안의 쿼리를 사용하는 방법이다.
@Test
public void subQuery() throws Exception {
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);
}
서브쿼리의 alias는 메인쿼리와 달라야한다.
JPA, JPQL의 한계로 from절의 서브쿼리는 지원하지 않는다. 따라서 QueryDsl도 지원하지 않는다. 하이버네이트 구현절 을 사용하면 select 서브쿼리는 사용한다.
해결책
1. 서브쿼리를 join으로 변경(안될수도 있음)
2. 어플리케이션에서 쿼리를 분리해서 2회 실행
3. nativeSQL을 사용한다
@Test
public void basicCase() {
List<String> result = queryFactory .select(member.age
.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.from(member)
.fetch();
}
@Test
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();
}
간단하게 when, then으로 해결할수도 있고
복잡한 when(조건문).then()으로 쓸 수도 있다.
@Test
public void constant() {
Tuple result = queryFactory
.select(member.username, Expressions.constant("A"))
.from(member)
.fetchFirst();
}
@Test
public void concat() {
String result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
}
문자를 더할때는 Expression,constant를 사용하고
문자열을 더해서 출력할떄는
concat을 이용한다.