1. 기본 조인
@Test
void join(){
List<Member> teamA = qf
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(teamA).extracting("username").containsExactly("member1","member2");
}
팀 이름이 teamA인 회원의 이름을 모두 조회하는 쿼리이다 . join() 메서드의 첫번째 파라미터로는 외래키가 되는 필드를 , 두번째 파라미터로는 조인할 엔티티의 Q - Type을 적어준다 ,inner 조인 ,outer 조인 모두 사용 가능하다.
(위의 코드는 모두 static import를 한 상태이기 때문에 위와 같이 사용이 가능 한 것이다 static import를 하지 않았다면 , Qteam.team, QMember.member 이런 식으로 써야함)
2.세타조인
연관관계가 전혀없는 테이블을 조인할때 세타조인을 사용한다 DB는 cross조인을 통해 연관관계가 없는 두 테이블을 모두 조인하여 조회하게된다 . 세타 조인의 경우 외부조인이 불가능하다 .
@Test
void theta_join(){
//멤버이름이 팀이름과 같은 회원 조회
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Member> result = qf.select(member)
.from(member, team)
.where(member.username.eq(team.name))
.fetch();
assertThat(result).extracting("username").containsExactly("teamA","teamB");
}
출력되는 쿼리
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_ cross
join
team team1_
where
member0_.username=team1_.name
크로스 조인이 일어난다 .
3. on절을 활용한 join
@Test
void join_on_filtering(){
List<Tuple> result = qf.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()을 통해 on 절 조건을 적어주면 된다. 위 같은경우 member 와 team 을 leftjoin하여 가져오고 on절 조건으로 team의 이름이 teamA인 것만 가져오도록 했다 . leftjoin이기 떄문에 member를 기준으로 데이터를 모두 가져오고 team은 team A에 해당하는 데이터만 가져올 거다 , 만약 팀이름이 teamA가 아니라면 null로 표시될 것이다.
내부 조인을 사용할 경우 공통되는 부분만 데이터가 가져와지기 때문에 leftjoin에서 null로 표시되었던 데이터 레코드는 사라진다.
이 경우 where 절로 조건을 주는 것과 결과가 같기 때문에 내부조인을 사용할 경우에는 익숙한 where 절로 조건을 주고 외부조인이 필요할 때 on 절을 활용하는 것이 좋다 .
4. 페치조인
@Test
void fetchJoin(){
em.flush();
em.clear();
Member findMember = qf.selectFrom(QMember.member)
.join(member.team , team).fetchJoin()
.where(QMember.member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치조인 미적용").isTrue();
}
조인문 뒤에 fetchJoin()을 달아주면 fetch 조인이 된다. fetch 조인은 필요한 데이터를 한번에 가져오고 싶을때 사용되고 쿼리에서 실행 되기 때문에 엔티티의 기본페치전략이 Lazy로 되어있어도 fetch 조인이 우선된다.
emf 는 @PesistanceUnit 을 통해 EntityManagerFactory를 담은 변수이다 EntityManagerFactory의 isLoaded()를 통해 해당 필드가 로딩되있는지 상태를 알 수 있다. 지정한 데이터를 한번에 가져오는 fetch조인이기 때문에 loaded는 true가 된다.
5.서브 쿼리
QueryDsl에서 서브쿼리를 사용하기 위해서는 com.querydsl.jpa.JPAExpressions를 사용해야한다 .
한계
- JPA JPQL 서브쿼리는 from 절의 서브쿼리는 지원하지 않는다.
해결 : - from 절의 서브쿼리는 다 그런건 아니지만 대부분 join 절로 교체할 수 있다
- 애플리케이션에서 쿼리를 2번 분리해서 실행한다 .
- nativeSQL을 사용한다.
from 절의 서브쿼리같은 경우 보통 화면에 맞춰 데이터를 가져올 경우에 사용되는데 , DB는 데이터를 정제해서 가져오는 역할만 하고 화면에 맞추는 것은 프레젠테이션 계층에서 담당하는 것으로 방향을 맞춰가는 것이 좋다 (복잡한 쿼리를 줄일 수 있다).
6. case문 사용
@Test
void basicCase(){
List<String> fetch = qf
.select(member.age
.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.from(member)
.fetch();
for (String s : fetch) {
System.out.println(s);
}
}
//caseBuilder 사용
@Test
public void complexCase(){
List<String> 기타 = qf
.select(new CaseBuilder()
.when(member.age.between(0, 20)).then("0~20")
.when(member.age.between(20, 30)).then("20~30")
.otherwise("기타")
)
.from(member)
.fetch();
for (String s : 기타) {
System.out.println(s);
}
}
간단한 경우 when.then 으로 사용하고
복잡한경우 caseBuilder를 사용해서 case문을 만든다 .
case문도 화면에 맞는 쿼리를 짤때 사용하는 경우가 많다 . 위의 from절 서브쿼리와 마찬가지로 화면에 맞추는 일은 화면에서 해결하는 방향으로 진행하는 것이 좋다 .
7.상수 , 문자열 더하기
@Test
void testName(){
List<Tuple> a = qf
.select(member.username, Expressions.constant("A"))
.from(member)
.fetch();
for (Tuple tuple : a) {
System.out.println("tuple = " + tuple);
}
}
Expressions.constant("A") 로 상수를 만들 수 있다.
@Test
void concat(){
String s = qf
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
System.out.println("s = " + s);
}
문자열을 더할 경우에 concat()을 사용한다 . String type 만 가능하기 떄문에 조회하는 컬럼이 String이 아니라면
stringValue()로 캐스팅 해줘야 concat이 가능하다. enum 타입 같은 경우에도 DB에서는 알 수 없기 때문에 String value로 바꿔주는 것이 좋다 .
'JPA > QueryDSL' 카테고리의 다른 글
QueryDSL 공부 6 - 동적쿼리 (booleanBuilder) (0) | 2021.06.19 |
---|---|
QueryDSL 공부 5 - 프로젝션과 결과 반환 (0) | 2021.06.19 |
QueryDSL 공부 3 - QueryDSL 활용1 (0) | 2021.06.18 |
QueryDSL 공부 2 - Q-Type 활용 (0) | 2021.06.18 |
QueryDSL 공부 1 - queryDSL 설정 (0) | 2021.06.18 |