createQuery
를 통해 직접 JPQL
을 작성하여 보내는 방식이다.
setParameter
를 통해 파라미터가 뭔지 직접 정해줘야한다.JPAQueryFactory
를 만들고,
QMember
를 통해 접근하는 방식이다.
처음에 Gradle
에서 compileQuerydsl
을 실행시켜서 빌드에 QMember
를 생성해야 사용이 가능하다.
eq
등 여러 메서드를 통해 설정이 가능하다.select, from
등을 잘못 입력했을때, 컴파일 시점에서 오류가 발생하므로 사전에 방지가 가능하다.또, JPAQueryFactory
의 경우,
이런 식으로 사전에 미리 필드에 만들어둬도 된다.
Q클래스 인스턴스를 사용하는 2가지 방법
이중에 가장 권장하는 방법은
이렇게 QMember.member
를 static Import
로 만들고 사용하는 것이다.
이 경우, new Member("member1")
이 자동으로 생성되는데,
같은 테이블을 조인해야하는 경우가 아니라면, 그냥 위와 같이 사용하면 된다.
member.username.eq("member1") // username = 'member1'
member.username.ne("member1") // username != 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() // 이름이 is not null
member.age.in(10, 20) // age in (10, 20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10, 30) // between 10, 30
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
member.username.like("member%") // like 검색
member.username.contains("member") // like '%member%' 검색
member.username.startsWith("member") // like 'member%' 검색
== eq
!= ne
< lt
> gt
<= le
>= ge
자주 쓰이는 메서드들이다. 메뉴얼 보면서 사용해보자.
요런 식으로 사용한다.
and
의 경우
이렇게 where
안에 끊어치기도 가능하다.
fetch()
: 리스트 조회, 데이터 없으면 빈 리스트 반환fetchOne()
: 단 건 조회null
com.querydsl.core.NonUniqueResultException
fetchFirst()
: limit(1).fetchOne()
fetchResults()
: 페이징 정보 포함, total count 쿼리 추가 실행fetchCount()
: count
쿼리로 변경해서 count 수 조회paging1
offset
부터 limit
까지 가져온다. 즉, 1부터 2까지 가져온다. 처음 시작은 0
paging2
getTotal()
전체 멤버수는 4명
getLimit
, getOffset
처음과 끝
getResults
페이징 결과 리스트
Querydsl
에 있는 Tuple
을 이용하여 서로 다른 타입의 결과값을 한곳에서 볼 수 있다.
하지만, 실무에서는 결과값을 Dto
로 변환할 수 있는 기능을 사용하기 때문에 이 기능을 자주 쓰지는 않는다고 한다.
.from(member)
: Member
에서 찾는다.
.select(team.name, member.age.avg())
: Team
이름과 Member
나이의 평균을 가져올 것이다.
.join(member.team, team)
: Member.team
과 Team
을 조인한다.
groupBy(team.name)
: Team
이름으로 그룹을 만들 것이다.
goupBy
, 그룹화된 결과를 제한하려면 having
...
.groupBy(item.price)
.having(item.price.gt(1000))
...
아이템 중에서 1000원이 넘는거만 뽑아라.
조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q타입을 지정하면 된다.
Member
에서 Team
연결이 안된 대상을 찾아올 수 있다.
from
절에 여러 엔티티를 선택해서 세타 조인ON절을 활용한 조인(JPA 2.1부터 지원)
1. 조인 대상 필터링
2. 연관관계 없는 엔티티 외부 조인
참고
내부조인을 할때는 where
절에서 필터링 하는 것과 기능이 동일하다.
그러므로 외부조인이 필요할때는 on
을 사용하고
내부조인은 where
을 사용해서 해결하자.
on
을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 내부조인도 가능.leftJoin(member.team, team)
from(member).leftJoin(team).on(xxx)
페치 조인은 SQL에서 제공하는 기능은 아니고. SQL조인을 활용해서 연관된 엔티티를 한번에 조회하는 기능이다.
주로 성능 최적화에 이용된다.
.fetchJoin()
/**
* 나이가 가장 많은 회원 조회
*/
@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);
}
/**
* 나이가 평균 이상인 회원
*/
@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
.select(memberSub.age.avg())
.from(memberSub))
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
from 절의 서브쿼리 한계
JPA JPQL
서브쿼리의 한계점은 from 절의 서브쿼리는 지원하지 않는다.
Querydsl
도 지원하지 않는다.
from 절의 서브쿼리 해결방안
1. 서브쿼리를 join
으로 변경한다.(가능한 상황도 있고, 불가능한 상황도 있음)
2. 어플리케이션에서 쿼리를 2번 분리해서 실행
3. nativeSQL
을 사용한다.
실시간 트래픽을 생각한다면, 한방 쿼리는 안좋을 수도 있다.
설계 문제이긴한데, 엄청 복잡하게 쿼리를 짜는 것보다,
쿼리를 2번 날리는게 더 나은 선택일수도 있다.
select, where에서 사용 가능
간단한 조건 vs 복잡한 조건
이걸 써야하나 고민을 할 수가 있다.
추천하는건 꼭 써야하는 경우가 아니라면 DB에서 해결하지 말고, 꺼낸 다음에 로직에서 해결하라.
@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);
}
//result
//tuple = [member1, A]
//tuple = [member2, A]
//tuple = [member3, A]
//tuple = [member4, A]
}
@Test
public void concat() {
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);
}
//result
//s = member1_10
}
참고
member.age.stringValue()
부분이 중요한데, 문자가 아닌 다른 타입들은 stringValue()
로 문자로 변환할 수 있다.
이 방법은 ENUM
처리할 때도 자주 사용한다.