QueryDSL 기본문법
실전! Querydsl - 인프런 | 강의
Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런...
www.inflearn.com
문법 관련해서는 이미 정리가 잘된 강의자료가 있지만 SpringBoot + JPA + QueryDSL을 사용하여 토이프로젝트를 진행하기 전에 강의 내용을 한번 더 정리하고 진행하기 위해 해당 포스팅을 작성한다.
기본 Q-Type 활용
Q클래스 인스턴스를 사용하는 방법으로 다음과 같은 2가지 방법이 있다.
- new 키워드를 사용하여 별칭을 직접 지정하여 생성하는 방법
- QType에 public static final 키워드로 미리 생성되어있는 기본 인스턴스를 사용하는 방법
QMember qMember = new QMember("m"); //별칭 직접 지정
QMember qMember = QMember.member; //기본 인스턴스 사용
같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하면 되며 QType을 static import 하여 사용하면 편리하다.
// 기본 인스턴스를 static import와 함께 사용하면 편리하다.
import static study.querydsl.entity.QMember.*;
...
public Member findByUserName(String userName) {
Member findMember = queryFactory.select(member)
.from(member)
.where(m.userName.eq(userName))
.fetchOne();
return findMember;
}
검색 조건 쿼리
select().from() 을 selectFrom() 으로 합칠 수 있다.
Member findMember = queryFactory.select(member).from(member)
//========================================================//
Member findMember = queryFactory.selectFrom(member)
.and() 또는 .or()를 메서드 체인으로 연결할 수 있다.
Member findMember = queryFactory.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.fetchOne();
where 절에 다중 파라미터를 넣으면 and조건으로 처리하며 값이 null인 경우 해당 조건이 무시되어 동적쿼리를 편리하게 작성할 수 있다.
Member findMember = queryFactory.selectFrom(member)
.where(member.username.eq("member1"), (member.age.eq(10))
.fetchOne();
JPQL이 제공하는 모든 검색 조건을 제공한다.
eq() | = | ne() | != | eq().not() | != |
isNotNull() | is not null | in() | in | notIn() | not in |
between() | between | goe() | >= | gt() | > |
loe() | <= | lt() | < | like() | like |
contains() | like(% || ' ' || %) | startwith() | like(' ' || %) |
정렬
- desc(), asc() : 일반 정렬
- nullsLast(), nullsFirst() : null 데이터 순서 부여
List<Member> result = queryFactory.selectFrom(member)
.where(member.age.eq(100))
.orderBy(member.age.desc(), member.username.asc().nullsLast())
.fetch();
결과 조회
메서드 | 설명 |
fetch() | 리스트를 조회한다. 데이터가 없는경우 빈리스트를 반환한다. |
fetchOne() | 단 건 조회한다. 결과가 없으면 null을 반환하고, 여러건이 반환되는 경우 com.querydsl.core.NonUniqueResultException 예외를 발생시킨다. |
fetchFirst() | 단 건 조회한다. 결과가 없으면 null을 반환하고, 여러 건이 조회되는경우 첫번째 행을 반환한다. |
|
|
|
* fetchResults()와 fetchCount()가 명확하게 동작하지 않는 이슈가 발생하여 QueryDSL 5.0부터 deprecated 되었다.
totalcount가 필요없다면 fetch()를 통해 페이징 처리하고 fetchCount() 대신 fetch().size()를 사용해야 한다.
페이징
fetch() 메서드를 사용하여 데이터 조회 시 offset()과 limit() 메서드를 이용해 조회 건 수를 제한할 수 있다.
public Page<Member> findMemberWithPaging(Pageable pageable) {
List<Member> content = queryFactory.selectFrom(member)
.orderBy(member.username.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
int totalCount = queryFactory.selectFrom(member)
.orderBy(member.username.desc())
.fetch().size();
return new PageImpl<>(content, pageable, totalCount);
}
집합
JPQL이 제공하는 모든 집합 함수를 제공한다.
@Test
void aggregation() {
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);
}
조인
기본 조인
- .join()
- .leftJoin()
- .rigthJoin()
첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q 타입을 지정하면 된다.
List<Member> result = queryFactory.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
on절을 활용하여 native query처럼 작성할 수도 있다. 이런경우 연관관계가 없는 엔티티 간의 조인이 가능하다
List<Tuple> result = queryFactory.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
연관관계가 없는 엔티티 간의 조인 시에는 leftJoin부분에 파라미터로 기준이 되지않는 엔티티만 들어간다.
List<Tuple> result = queryFactory.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
세타 조인
join 키워드를 사용하지않고 연관관계가 없는 필드 간의 innerJoin이 가능하다. (outerJoin은 불가능)
List<Member> result = queryFactory.select(member)
.from(member, team)
.where(member.username.eq(team.name))
.fetch();
Fetch 조인
join(), leftJoin() 등 조인 메서드 뒤에 .fetchJoin() 키워드를 사용하여 즉시로딩으로 조회할 수 있다.
Member findMember = queryFactory.selectFrom(member)
.join(member.team,team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
서브쿼리
com.querydsl.jpa.JPAExpressions 클래스를 사용하여 select절과 where절에서 서브쿼리를 사용할 수 있다.
아쉽게도 from 절에서는 기능상의 문제로 사용할 수 없다고한다.
where절에서의 서브쿼리
List<Tuple> fetch = queryFactory.select( member.username
, JPAExpressions.select(memberSub.age.avg())
.from(memberSub))
.from(member)
.fetch();
select절에서의 서브쿼리
import static com.querydsl.jpa.JPAExpressions.select;
...
List<Tuple> fetch = queryFactory.select( member.username
, select(memberSub.age.avg())
.from(memberSub))
.from(member)
.fetch();
select절에서의 예제처럼 import static 키워드를 사용하여 편리하게 사용할 수 있다.
Case 문
일반적인 방법과 CaseBuilder 객체를 사용하여 복잡한 조건을 정의하는 방법을 지원한다.
-일반적인 방법-
List<String> result = queryFactory.select(member.age.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.from(member)
.fetch();
-CaseBuilder 객체를 사용하는 방법-
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();
상수, 문자 더하기
Tuple result = queryFactory.select(member.username, Expressions.constant("A"))
.from(member)
.fetchFirst();
String result = queryFactory.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
문자가 아닌 타입들은 stringValue() 메서드를 통해 문자 타입으로 변경할 수 있다.