본문 바로가기

Advance I/Querydsl

22.07.13

집합

집합함수

count(m) -> m.count()

sum(m.age) -> m.age.sum()

avg(m.age) -> m.age.avg()

max(m.age) -> m.age.max()

min(m.age) -> m.age.min()  

select 항목을 querydsl.Tuple List로 조회한다.

 

tuple.get(m.count())

tuple.get(m.age.sum()) ... 식으로 각 데이터를 뽑아낸다. 

GroupBy

.groupBy(그룹핑하고자 하는 컬럼)

집합 함수 select시 그룹 내에서 함수 적용

 

* groupBy(), having() 예시*

  .groupBy(item.price)

  .having(item.price.gt(1000))

조인

기본 조인

join(조인 대상, 별칭으로 사용할 Q타입)

 

join() , innerJoin() : 내부 조인(inner join)

leftJoin() : left 외부 조인(left outer join)

rightJoin() : rigth 외부 조인(rigth outer join)

 

JPQL의 on 과 성능 최적화를 위한 fetch 조인 제공 -> on 절에서 설명

세타 조인

연관관계가 없는 필드의 조인

from 절에 여러 엔티티를 선택해서 세타 조인

외부 조인 불가능 -> on 절을 사용하면 외부 조인 가능

 

ex. 회원의 이름-팀의 이름

.from(member, team) //테이블의 모든 컬럼 가져옴. cross join

.where(member.username.eq(team.name)) //조건절을 통해 원하는 데이터를 추림

 

참고: 기존의 회원-팀 연관관계: join(QMember.member.team, QTeam.team)

on절

ON절을 활용한 조인(JPA 2.1부터 지원)

1. 조인할 대상 필터링

2. 연관관계 없는 엔티티 외부 조인

 

조인할 대상 필터링

JPQL: SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'teamA'

Querydsl: .select(member, team).from(member).leftJoin(member.team, team).on(team.name.eq("teamA"))

 

Member는 모두 가져오고, Team은 이름이 "teamA"인 Team만 가져온다.

left join이기 때문에 팀이 "teamB"인 회원도 null로 모두 가져왔다. (내부 조인이면 조인 대상이 null인 row는 가져오지 않음)

 

member.team과 team의 id를 매칭하고, and 절로 team.name을 "teamA"와 매칭

ON m.TEAM_ID=t.id and t.name='teamA'

 

참고:  on 절을 활용해 조인 대상을 필터링 할 때, 내부조인(inner join)을 사용하면, where 절에서 필터링 하든지, on 절을 활용든지 기능이 동일하다. 따라서 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 on절을 사용하자.

 

연관관계 없는 엔티티 외부 조인

JPQL: SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name

Querydsl: .select(member, team).from(member).leftJoin(team).on(member.username.eq(team.name))

 

Member는 모두 가져오고, 회원 이름과 팀 이름이 같은 Team만 가져온다. 

left join이기 때문에 회원 이름과 팀 이름이 같지 않은 팀도 null로 모두 가져왔다. 

연관관계가 없기 때문에 id 매칭 없음

 

하이버네이트 5.1부터 on 을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었다.

물론 내부 조인도 가능하다.

 

주의! 문법을 잘 봐야 한다. leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.

-일반조인: leftJoin(member.team, team)

-on조인: from(member).leftJoin(team).on(xxx)

페치 조인

지연 로딩 최적화

페치 조인은 SQL에서 제공하는 기능은 아니다. JPA를 통해 연관된 엔티티를 SQL 한번에 조회하는 기능이다. 주로 성능 최적화에 사용하는 방법이다.

 

참고: 페치 조인 테스트시, 영속성 컨텍스트를 clear한 후 하는 것이 좋다.

 

페치 조인 미적용: 회원 조회 시 연관된 팀 엔티티는 함께 조회하지 않는다. (팀 엔티티를 조회하려면 초기화 과정 필요)

 

selectFrom(member).join(member.team, team).fetchJoin()

페치 조인 적용: 회원 조회 시 연관된 팀 엔티티까지 함께 조회

내부 조인이든 외부 조인이든 뒤에 .fetchJoin()만 추가

서브 쿼리

QueryFactory 대신 com.querydsl.jpa.JPAExpressions 사용

서브 쿼리이기 때문에 같은 엔티티 사용시 별칭이 중복되면 안된다.

다른 별칭을 가진 새로운 QEntity 객체를 생성하여 사용한다.

(Q class가 static으로 제공하는 인스턴스의 별칭은 "엔티티명 (소문자)")

참고: JPAExpressions를 static import 하여 코드를 깔끔하게 유지 가능

 

from 절의 서브쿼리 한계

JPA JPQL 서브쿼리의 한계점으로 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다. 당연히 Querydsl 도 지원하지 않는다. 하이버네이트 구현체를 사용하면 select 절의 서브쿼리는 지원한다. Querydsl도 하이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.

 

from 절의 서브쿼리 해결방안

1. 서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)

2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.

3. nativeSQL을 사용한다.

 

화면에 맞춘 API 용 쿼리를 짜다보면 from 절 서브 쿼리가 다중으로 필요할 수 있다.

DB는 최소한의 데이터를 조회해오는 용도로만 사용하고, 화면에 맞추는 작업은 애플리케이션에서 하면 복잡한 쿼리를 줄일 수 있다. 한 번의 쿼리로 모든 것을 해결하고자 고집하기 보다는, 애플리케이션 스펙에 따라 2번, 3번에 나누어 쿼리할 때 심플하게 문제를 해결하는 방법일 수 있다.

Case 문

select, 조건절(where), order by에서 사용 가능

 

.when().then()

복잡한 조건은 CaseBuilder() 사용

 

정말 필요할까?

필터링, 그룹핑, 집합 함수 정도만 DB를 통하고, 실제 데이터 값에 따른 작업은 애플리케이션에서 하자.

상수, 문자 더하기

상수

Expressions.constant("상수") 사용

 

참고: 최적화가 가능하면 SQL에 constant 값을 넘기지 않는다. JPQL에 상수가 포함되지 않고 상수 형태로 출력만 한다. 상수를 더하는 것 처럼 최적화가 어려우면 SQL에 constant 값을 넘긴다.

 

문자 더하기

concat() 을 체인으로 연달아 호출

ex. member.username.concat("_").concat(member.age.stringValue())

   결과: member.username + "_" + (char)member.age

 

참고: member.age.stringValue() 부분이 중요한데, 문자가 아닌 다른 타입들은 stringValue() 로 문자로 변환할 수 있다. 이 방법은 ENUM을 처리할 때도 자주 사용한다.

중급 문법

프로젝션과 결과 반환 - 기본

프로젝션: select 대상 지정

프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음

프로젝션 대상이 둘 이상이면 Tuple이나 DTO로 조회

프로젝션 대상이 하나

List result<String> = queryFactory.select(member.username).from(member).fetch();

튜플 조회

com.querydsl.core.Tuple

프로젝션 대상이 둘 이상일 때 사용

select member.username, member.age

반환: List<Tuple> result, 1 row 당 1 tuple 

->String username = tuple.get(member.username)

->int age = tuple.get(member.age)

 

튜플은 repository 안에서만 사용하고 반환할 때는 DTO로 변환하는 것을 권장

 

 

'Advance I > Querydsl' 카테고리의 다른 글

22.07.21  (0) 2022.07.21
22.07.20  (0) 2022.07.20
22.07.18  (0) 2022.07.18
22.07.14  (0) 2022.07.14
22.07.07 :: 실전! Querydsl  (0) 2022.07.07