- 조인 - 기본 조인
/*
* 팀 A에 소속된 모든 회원
* */
@Test
public 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(member.team, team)을 통해 join을 할 수 있다. join만 작성하게 되면 inner join을 기본으로 한다.
- 조인 - ON절
/*
* 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* JPQL : select m, t from Member m left join m.team t on t.name = 'teamA'
* */
@Test
public void join_on_filtering() {
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
// .leftJoin(member.team, team).on(team.name.eq("teamA"))
.join(member.team, team)
// .on(team.name.eq("teamA")) // where절을 사용하는 것과 동일하다.
.where(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.join(member.team, team)
- leftJoin의 경우 주체가 되는 엔티티의 모든 member 정보를 전부 불러오지만, inner join의 경우 두 엔티티가 전부 부합하는 데이터에 대해서만 결과 값을 가져온다.
- left Join
- member 테이블에 있는 총 4개의 엔티티를 가져오는 것을 확인할 수 있다.
- inner Join
- 추가로 inner join을 사용할 때는 on 대신 where을 사용할 수 있다.
- where을 사용하든 on을 사용하든 결과는 동일하다.
- 세타 조인
- 세타 조인이란 연관관계가 없는 필드로 조인하는 것을 뜻한다.
/*
* 연관관계가 없는 엔티티 외부 조인
* 회원의 이름이 팀 이름과 같은 대상 외부 조인
* */
@Test
public void join_on_no_relation() {
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))
//join 방식이 다르다. member.team, team과 같이 join하는 것이 아니다.
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
- select에서 member와 team을 같이 조회한다.
- 일반적으로 left Join을 하게 될 경우 (member.team, team)과 같이 연관된 데이터끼리 묶어 join을 한다.
- 하지만 세타 조인의 경우 leftJoin(team)과 같이 from에 연관된 엔티티와 관련 없는 엔티티를 left Join 값으로 넣는다.
- 세타 조인을 할 경우 모든 경우 수에 대해 DB가 전부 join을 하게 된다.
- on을 통해 두 테이블에서 조건에 만족하는 값을 결과로 가져온다.
- 페치 조인
- LAZY 지연 로딩이 적용되지 않은 경우
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() {
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();
// Team의 경우 LAZY이고 fetch join을 적용하지 않았기 때문에
// 당연히 team에 대한 fetch 조인 및 조회는 발생하지 않는다.
}
- Member, Team 연관관계에서 Team의 경우 LAZY 즉, 지연 로딩으로 설정되어 있기 때문에 Team 속성에 대해 조회하지 않으면 Team에 대한 쿼리를 날리지 않는다.
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 미적용").isFalse();
- 해당 코드는 페치 조인이 올바르게 적용되었는지 확인하는 코드이다.
- 페치 조인이 적용되지 않았기 때문에 False로 테스트 했을 때 정상적으로 통과한다.
- 페치 조인 적용
@Test
public void fetchJoinUse() {
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() 뒤에 .fetch()를 붙이면 fetch join이 적용된다.
- 서브 쿼리
- JPAExpressions를 통해 서브 쿼리를 작성한다.
- JPA, JPQL의 한계점은 from 절의 서브쿼리를 지원하지 않기 때문에 Querydsl도 마찬가지로 from 절의 서브쿼리를 지원하지 않는다.
/*
* 나이가 가장 많은 회원 조회
* */
@Test
public void subQuery() {
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);
}
- 메인 쿼리와 서브 쿼리의 별칭이 겹치면 안되기 때문에
new QMember("memberSub")
와 같이 새롭게 QMember 객체를 생성해주고 사용해야 한다.
/*
* 나이가 평균 이상인 회원
* */
@Test
public void subQueryGoe() {
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);
}
/*
* 나이가 평균 이상인 회원
* */
@Test
public void subQueryIn() {
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);
}
@Test
public void selectSubQuery() {
QMember memberSub = new QMember("memberSub");
List<Tuple> result = queryFactory
.select(member.username,
JPAExpressions //static import가 가능하다.
.select(memberSub.age.avg())
.from(memberSub)
)
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
- Case문
@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);
}
}
- .when / .otherwise를 통해 Case문을 작성 할 수 있다.
@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();
for (String s : result) {
System.out.println("s = " + s);
}
}
- 상수, 문자 더하기
@Test
public void constant() {
List<Tuple> result = queryFactory
.select(member.username, Expressions.constant("A"))
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
- Expressions.contant를 통해 원하는 상수 값을 출력 데이터에 추가할 수 있다.
- Expressions.constant를 사용하면 JPQL에서는 관련 쿼리가 나가지 않고, 결과에 대해서만 해당 결과를 포함시켜서 출력한다.
@Test
public void concat() {
//{username}_{age}
List<String> result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
- concat을 사용하여 결과값을 붙여서 출력하는 경우가 자주 발생한다.
- 반환타입이 일치하지 않으면 concat을 통해 이어붙이는 것이 불가능하다.
- age의 경우 String 타입이 아니기 때문에 stringValue()를 통해 String 타입으로 변환 시켜서 이어 붙여준다.
- enum(열거형) 사용할 때 stringValue()를 많이 사용한다.
출처 : 인프런 - 김영한(실전! Querydsl)
'Programming > Querydsl' 카테고리의 다른 글
Querydsl - 중급 문법 (2) 동적 쿼리 (0) | 2023.07.28 |
---|---|
Querydsl - 중급 문법 (1) (프로젝션) (0) | 2023.07.28 |
Querydsl - 기본 문법 (2) (결과 조회, 정렬, 페이징, 집합) (0) | 2023.07.27 |
Querydsl - 기본 문법 (1) (기본 Q 타입, 검색, AND) (0) | 2023.07.26 |
Querydsl - Querydsl 설정 (0) | 2023.07.26 |