/**
* JPQL
* select
* COUNT(m), //회원수
* SUM(m.age), //나이 합
* AVG(m.age), //평균 나이
* MAX(m.age), //최대 나이
* MIN(m.age) //최소 나이
* from Member m
*/
@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);
}
GroupBy
사용/**
* 팀의 이름과 각 팀의 평균 연령을 구해라.
*/
@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);
}
groupBy
를 사용하여 그룹별로 원하는 결과를 얻어낼 수 있다.having
을 사용할 수 있다..groupBy(item.price)
.having(item.price.gt(1000))
: item
의 price
를 기준으로 그룹핑을 하되, 가격이 1000
보다 큰 값만 그룹핑한다.
join(조인 대상, 별칭으로 사용할 Q타입)
/**
* 팀 A에 소속된 모든 회원
*/
@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");
}
join()
, innerJoin()
: 내부 조인leftJoin()
: left 외부 조인rightJoin()
: right 외부 조인on
과 성능 최적화를 위한 fetch
조인 제공연관관계가 없는 필드로 조인
/**
* 세타 조인(연관관계가 없는 필드로 조인)
* 회원의 이름이 팀 이름과 같은 회원 조회
*/
@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");
}
from
절에 여러 엔티티를 선택해서 세타 조인on
을 사용하면 외부 조인 가능on
절 ]📌
ON
절을 활용한 조인 (JPA 2.1부터 지원)
- 조인 대상 필터링
- 연관관계 없는 엔티티 외부 조인
Ex. 회원과 팀을 조인하면서, 팀 이름이
teamA
인 팀만 조인, 회원은 모두 조회
/**
* 예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* 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 {
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
절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자!
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]
Ex. 회원과 이름과 팀의 이름이 같은 대상 외부 조인
/**
* 연관관계가 없는 엔티티 외부 조인
* 회원의 이름이 팀 이름과 같은 대상을 외부 조인
*
* @throws Exception
*/
@Test
public void join_on_no_relation() throws Exception {
//given
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
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("tuple = " + tuple);
}
on
절을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 물론 내부 조인도 가능하다.🚫 문법 주의 ❗❗
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 한번에 조회하는 기능이다.
주로 성능 최적화에 사용하는 방법!
지연 로딩으로 Member, Team SQL 쿼리 각각 실행
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() throws Exception {
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
은 조회 ❌즉시 로딩으로 Member, Team SQL 쿼리 조인으로 한번에 조회
@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();
}
join()
, leftJoin()
등 조인 기능 뒤에 fetchJoin()
이라고 추가하면 된다.
com.querydsl.jpa.JPAExpressions
사용
eq
사용/**
* 나이가 가장 많은 회원 조회
*/
@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);
}
goe
사용
goe
: 크거나 같은
/**
* 나이가 평균 나이 이상인 회원
*/
@Test
public void subQueryGoe() throws Exception {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.goe(
JPAExpressions
.select(memberSub.age.avg())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(30,40);
}
in
사용/**
* 서브쿼리 여러 건 처리, in 사용
*/
@Test
public void subQueryIn() throws Exception {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.in(
JPAExpressions
.select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(20, 30, 40);
}
select
절에 subquery
List<Tuple> fetch = queryFactory
.select(member.username,
JPAExpressions
.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(JPAExpressions.select(memberSub.age.avg()).from(memberSub)));
}
static import
활용import static com.querydsl.jpa.JPAExpressions.select;
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
select(memberSub.age.max())
.from(memberSub)
))
.fetch();
📌
from
절의 서브쿼리 한계JPA JPQL, Qeurydsl에서 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다.
💡
from
절의 서브쿼리 해결방안
- 서브쿼리를 join으로 변경한다. → 가능할 때도, 불가능할 때도 있다.
- 애플리케이션에서 쿼리를 2번 분리해서 실행한다.
- nativeSQL을 사용한다
Case
문 ]
select
, 조건절(where
),order by
에서 사용 가능
List<String> result = queryFactory
.select(member.age
.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.from(member)
.fetch();
age
가
10
이면 열살
출력20
이면 스무살
출력기타
출력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();
CaseBuilder()
를 통해 동작한다.when
절 안에 조건이 들어간다.Expressions.constanc(xxx)
사용Tuple result = queryFactory
.select(member.username, Expressions.constant("A"))
.from(member)
.fetchFirst();
위와 같이 최적화가 가능하면 SQL에 constant
값을 넘기지 않는다.
상수를 더하는 것처럼 최적화가 어려우면 SQL에 constant 값을 넘긴다.
concat
String result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
member.age.stringValue()
부분 중요! ⭐
stringValue()
로 문자로 변환할 수 있다.ENUM
을 처리할 때도 자주 사용한다.