[Querydsl] 기본 문법2
도메인 모델과 테스트 기본 코드는 전 포스트 내용을 이어간다.
예제 도메인 모델
목차
- 결과 조회
fetch()
fetchOne()
fetchFirst()
- 정렬
- 페이징
- 집합
- 집합 함수
- GroupBy
- 조인
- 기본 조인
- 세타 조인
- ON절
- 패치 조인
결과 조회
fetch()
: 리스트 조회, 데이터 없으면 빈 리스트 반환fetchOne()
: 단 건 조회- 결과가 없으면 :
null
- 결과가 둘 이상이면 :
com.querydsl.core.NonUniqueResultException
- 결과가 없으면 :
fetchFirst()
:limit(1).fetchOne()
//List
List<Member> fetch = queryFactory
.selectFrom(member)
.fetch();
//단 건
Member findMember1 = queryFactory
.selectFrom(member)
.fetchOne();
//처음 한 건 조회
Member findMember2 = queryFactory
.selectFrom(member)
.fetchFirst();
정렬
- 회원 정렬 순서
- 1.회원 나이 내림차순(desc)
- 2.회원 이름 올림차순(asc)
- 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
@Test 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(); }
desc()
,asc()
: 일반 정렬nullsLast()
,nullsFirst()
:null
데이터 순서 부여
페이징
조회 건수 제한
@Test
void Paging() {
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2) // 최대 2건 조회
.fetch();
}
집합
집합 함수
/**
* JPQL
* select
* COUNT(m), //회원수
* SUM(m.age), //나이 합
* AVG(m.age), //평균 나이
* MAX(m.age), //최대 나이
* MIN(m.age) //최소 나이
* from Member m
*/
@Test
void aggregation() {
List<Tuple> result = queryFactory
.select(member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min())
.from(member)
.fetch();
}
- JPQL이 제공하는 모든 집합 함수를 제공한다.
- tuple은 여러 타입을 받아올 때 사용한다고 보면 된다.(자세한 내용은 뒤에서…)
GroupBy
// 팀의 이름과 각 팀의 평균 연령을 구해라.
@Test
void group() {
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team)
.groupBy(team.name)
.fetch();
}
groupBy()
그룹화된 결과를 제한하려면having
GroupBy, having 예시
...
.groupBy(item.price)
.having(item.price.gt(1000))
...
조인
기본 조인
join(조인 대상, 별칭으로 사용할 Q타입)
// 팀 A에 소속된 모든 회원
@Test
void join() {
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
join()
,innerJoin()
: 내부 조인(inner join)leftJoin()
: left 외부 조인(left outer join)rightJoin()
: right 외부 조인(right outer join)-
JPQL의
on
과 성능 최적화를 위한fetch
조인 제공 extracting()
: 특정 필드를 추출하여 테스트
세타 조인
연관관계가 없는 필드로 조인
/**
* 세타 조인(연관관계가 없는 필드로 조인)
* 회원의 이름이 팀 이름과 같은 회원 조회
*/
@Test
void theta_join() {
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");
}
- from 절에 여러 엔티티를 선택해서 세타 조인
- 외부 조인 불가능 -> on을 사용하면 외부 조인 가능
ON절
- ON절을 활용한 조인(JPA 2.1부터 지원)
- 조인 대상 필터링
- 연관관계 없는 엔티티 외부 조인
조인 대상 필터링
/*
* 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* JPQL : select m, t from Member m left join m.team t on t.name = "teamA"
*/
@Test
void join_on_filtering() {
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
}
결과
t=[Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
t=[Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]
t=[Member(id=5, username=member3, age=30), null]
t=[Member(id=6, username=member4, age=40), null]
[참고]
on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다. 따라서, on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인이면 익숙한 where 절로 해결하고, 외부조인이 필요한 경우에만 이 기능을 사용하자.
연관관계 없는 엔티티 외부 조인
/*
* 연관관계가 없는 엔티티 외부 조인
* 회원의 이름이 팀 이름과 같은 대상 외부 조인
*/
@Test
void join_on_no_relation() {
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();
}
- 주의! 문법을 잘 봐야한다. leftJoin()부분에 일반 조인과 다르게 엔티티 하나만 들어간다.
- 일반 조인:
leftJoin(member.team, team)
- on 조인 :
from(member).leftJoin(team).on(xxx)
- 일반 조인:
결과
t=[Member(id=3, username=member1, age=10), null]
t=[Member(id=4, username=member2, age=20), null]
t=[Member(id=5, username=member3, age=30), null]
t=[Member(id=6, username=member4, age=40), null]
t=[Member(id=7, username=teamA, age=0), Team(id=1, name=teamA)]
t=[Member(id=8, username=teamB, age=0), Team(id=2, name=teamB)]
패치 조인
패치 조인은 SQL에서 제공하는 기능은 아니다. SQL조인을 활용해서 연관된 엔티티를 SQL 한번에 조회하는 기능이다. 주로 성능 최적화에 사용하는 방법이다.
패치 조인 미적용
@PersistenceUnit EntityManagerFactory emf;
// 지연 로딩으로 Member, Team SQL 쿼리 각각 실행
@Test
void no_fetchJoin() {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("패치 조인 미적용").isFalse();
}
패치 조인 적용
// 패치 조인으로 Member, Team SQL 쿼리 한번에 조회
@Test
void use_fetchJoin() {
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();
}
join(), leftJoin()
등 조인 기능 뒤에fetchJoin()
이라고 추가하면 된다.
<출처 : 인프런 - 실전! Querydsl(김영한)>
댓글남기기