환경설정

검증용 Q 타입 생성
Gradle IntelliJ 사용법
Gradle Tasks build clean
Gradle Tasks other compileQuerydsl

Gradle 콘솔 사용법
./gradlew clean compileQuerydsl

Q 타입 생성 확인
build generated querydsl
study.querydsl.entity.QHello.java 파일이 생성되어 있어야 함

> 참고: Q타입은 컴파일 시점에 자동 생성되므로 버전관리(GIT)에 포함하지 않는 것이 좋다. 앞서 설정에서
생성 위치를 gradle build 폴더 아래 생성되도록 했기 때문에 이 부분도 자연스럽게 해결된다. (대부분
gradle build 폴더를 git에 포함하지 않는다.)

기본검색

@Test
public void search() {
 Member findMember = queryFactory
 .selectFrom(member)
 .where(member.username.eq("member1")
 .and(member.age.eq(10)))
 .fetchOne();
 assertThat(findMember.getUsername()).isEqualTo("member1");
}

// 검색 조건은 .and() , . or() 를 메서드 체인으로 연결할 수 있다.
// 참고: select , from 을 selectFrom 으로 합칠 수 있음

* JPQL이 제공하는 모든 검색 조건 제공
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%’ 검색
...

* AND 조건을 파라미터로 처리
@Test
public void searchAndParam() {
 List<Member> result1 = queryFactory
 .selectFrom(member)
 .where(member.username.eq("member1"),
 member.age.eq(10))
 .fetch();
 assertThat(result1.size()).isEqualTo(1);
}

* where() 에 파라미터로 검색조건을 추가하면 AND 조건이 추가됨
* 이 경우 null 값은 무시 메서드 추출을 활용해서 동적 쿼리를 깔끔하게 만들 수 있음

결과 조회

fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne() : 단 건 조회
결과가 없으면 : null
결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount() : count 쿼리로 변경해서 count 수 조회

//List
List<Member> fetch = queryFactory
 .selectFrom(member)
 .fetch();
//단 건
Member findMember1 = queryFactory
 .selectFrom(member)
 .fetchOne();
//처음 한 건 조회
Member findMember2 = queryFactory
 .selectFrom(member)
 .fetchFirst();
//페이징에서 사용
QueryResults<Member> results = queryFactory
 .selectFrom(member)
 .fetchResults();
//count 쿼리로 변경
long count = queryFactory
 .selectFrom(member)
 .fetchCount();

정렬

/**
 * 회원 정렬 순서
 * 1. 회원 나이 내림차순(desc)
 * 2. 회원 이름 올림차순(asc)
 * 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
 */
@Test
public 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();
}

* desc() , asc() : 일반 정렬
* nullsLast() , nullsFirst() : null 데이터 순서 부여

페이징

// 조회 건수 제한
@Test
public void paging1() {
 List<Member> result = queryFactory
 .selectFrom(member)
 .orderBy(member.username.desc())
 .offset(1) //0부터 시작(zero index)
 .limit(2) //최대 2건 조회
 .fetch();
 assertThat(result.size()).isEqualTo(2);
}

// 전체 조회 수가 필요하면?
@Test
public void paging2() {
 QueryResults<Member> queryResults = queryFactory
 .selectFrom(member)
 .orderBy(member.username.desc())
 .offset(1)
 .limit(2)
 .fetchResults();
 assertThat(queryResults.getTotal()).isEqualTo(4);
 assertThat(queryResults.getLimit()).isEqualTo(2);
 assertThat(queryResults.getOffset()).isEqualTo(1);
 assertThat(queryResults.getResults().size()).isEqualTo(2);
}

* 주의: count 쿼리가 실행되니 성능상 주의!
* > 참고: 실무에서 페이징 쿼리를 작성할 때, 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만, 
count 쿼리는 조인이 필요 없는 경우도 있다. 그런데 이렇게 자동화된 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);
}
* JPQL이 제공하는 모든 집합 함수를 제공한다.
* tuple은 프로젝션과 결과반환에서 설명한다.

GroupBy 사용