FireDrago
[QueryDSL] 기본 문법 1 본문
QueryDSL은 코드 기반의 쿼리 작성 도구로, 문자열 기반 JPQL 대신 자바 코드를 통해 쿼리를 작성한다.
이를 통해 다음과 같은 장점을 제공한다.
1. 코드 가독성 향상
가장 큰 장점은 코드 가독성 향상이다. 문자열 기반 쿼리는 이해하기 어려울 수 있지만,
QueryDSL은 자연스러운 자바 코드로 쿼리를 작성할 수 있어 코드를 읽고 이해하기 쉽다.
특히 복잡한 쿼리일수록 이 효과가 더욱 두드러진다.
2. 컴파일 시점 오류 검증
QueryDSL은 컴파일 시점에 쿼리 문법 오류를 검증한다. 런타임 오류 발생 가능성을 줄여 코드 안정성을 높일 수 있다.
또한, IDE의 자동 완성 기능을 활용하여 쿼리 작성 속도를 높일 수 있다.
3. 재사용 가능한 쿼리 로직
조건절이나 정렬 기준 등을 메서드 형태로 작성하여 쿼리 로직을 재사용할 수 있다.
코드 중복을 줄이고 유지 관리 편의성을 높인다.
4. 안전한 데이터 접근
QueryDSL은 SQL 인젝션 공격으로부터 보호하는 기능을 제공한다.
setParameter() 메서드를 사용하지 않아도 내부에서 파라미터를 사용한다.
검색조건 쿼리
@Test
void search() {
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10))) // and() 사용
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
void searchAndParam() {
Member findMember = queryFactory
.selectFrom(member)
.where( // and의 경우 파라미터로 넘길수도 있다.
member.username.eq("member1"),
member.age.eq(10)
)
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
and 조건의 경우 .and() 를 사용하거나 where 절에서 파라미터로 넘길수 있다.
JPQL이 제공하는 모든 검색조건을 QueryDsl 로 표현할 수 있다.
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%’ 검색
결과조회
| 결과조회 메서드 | 설명 |
| fetch() | 리스트 조회, 데이터가 없다면 빈 리스트 반환 |
| fetchOne() | 단 건조회, 결과 없으면 null, 결과가 둘 이상이면 com.querydsl.core.NonUniqueResultException |
| fetchFirst() | limit(1).fetchOne() 최초값 단건조회 |
| fetchResults() | 페이징 정보 포함, total count 쿼리 추가실행 |
| fetchCount() | count 쿼리로 변경해서 count 수 조회 |
@Test
void resultFetch() {
// List 조회 -> fetch()
List<Member> fetch = queryFactory
.selectFrom(member)
.fetch();
// 단건조회 -> fetchOne()
Member findMember1 = queryFactory
.selectFrom(member)
.fetchOne();
// 처음 한 건 조회
Member findMember2 = queryFactory
.selectFrom(member)
.fetchFirst();
// 페이징에서 사용
QueryResults<Member> results = queryFactory
.selectFrom(member)
.fetchResults();
long total = results.getTotal();
List<Member> contents = results.getResults();
// count 쿼리로 변경
long count = queryFactory.selectFrom(member)
.fetchCount();
}
정렬
desc() , asc() : 일반 정렬
nullsLast(), nullsFirst() : null 데이터 순서 부여
/**
* 회원 정렬 순서
* 1. 회원 나이 내침차순 (desc)
* 2. 회원 이름 올림차순 (asc)
* 단 2에서 회원 이름이 없으면 마지막에 출력 (nulls last)
*/
@Test
void sort() {
em.persist(new Member(null, 100));
em.persist(new Member("member5", 100));
em.persist(new Member("member6", 100));
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(100))
// 나이 내림차순 -> 이름 오름차순 + 이름 없으면 마지막에 출력
.orderBy(member.age.desc(), member.username.asc().nullsLast())
.fetch();
Member member5 = result.get(0);
Member member6 = result.get(1);
Member memberNull = result.get(2);
assertThat(member5.getUsername()).isEqualTo("member5");
assertThat(member6.getUsername()).isEqualTo("member6");
assertThat(memberNull.getUsername()).isNull();
}
페이징
// **목적:** 단순 데이터 조회 테스트 (페이징 정보 없음)
// **조건:**
// - member 테이블에서 모든 데이터를 username 기준 내림차순으로 조회
// - offset 1, limit 2를 적용하여 2번째 데이터부터 2개의 데이터만 가져옴
@Test
void paging1() {
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetch();
assertThat(result.size()).isEqualTo(2);
}
// **목적:** 페이징 정보 포함 조회 테스트
// **조건:**
// - member 테이블에서 모든 데이터를 username 기준 내림차순으로 조회
// - offset 1, limit 2를 적용하여 2번째 데이터부터 2개의 데이터만 가져옴
// - QueryResults 객체를 사용하여 페이징 정보 (전체 조회 수, 페이지당 데이터 수, 시작 위치, 결과 목록)를 얻음
@Test
void paging2() {
QueryResults<Member> queryResult = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetchResults();
assertThat(queryResult.getTotal()).isEqualTo(4);
assertThat(queryResult.getLimit()).isEqualTo(2);
assertThat(queryResult.getOffset()).isEqualTo(1);
assertThat(queryResult.getResults().size()).isEqualTo(2);
}
페이징 쿼리를 작성할때 데이터 조회 쿼리는 여러 테이블을 조인해야 하지만, count 쿼리는 조인이 필요없는 경우가 있다.
그런데 fetchResult() 를 사용하면 count 쿼리역시 모두 조인을 하기 때문에 성능이 안나올 수 있다.
count 쿼리에 조인이 필요없는 성능최적화가 필요하다면, count 전용쿼리를 별도로 작성해야 한다.
집합
/**
* JPQL
* select
* COUNT(m), //회원수
* SUM(m.age), //나이 합
* AVG(m.age), //평균 나이
* MAX(m.age), //최대 나이
* MIN(m.age) //최소 나이
* from Member m
*/
@Test
public void aggregation() throws Exception {
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);
}
/**
* 팀의 이름과 각 팀의 평균 연령을 구하라
*/
@Test
void group() throws Exception {
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team)
.groupBy(team.name)
.fetch();
Tuple teamA = result.get(0);
Tuple teamB = result.get(1);
assertThat(teamA.get(team.name)).isEqualTo("teamA");
assertThat(teamA.get(member.age.avg())).isEqualTo(15);
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}
QueryDsl 은 JPQL이 제공하는 모든 집합 함수를 제공한다.
Group By 사용할때 그룹화된 결과를 제한하려면 having() 을 사용하면 된다.
.groupBy(item.price)
.having(item.price.gt(1000))
'프로그래밍 > QueryDSL' 카테고리의 다른 글
| [QueryDSL] QueryDSL 사용법 (0) | 2024.03.12 |
|---|
