스프링 부트 + 스프링 데이터 JPA 의 한계점
=> 복잡한 쿼리, 동적 쿼리
Querydsl
쿼리를 자바 코드로 작성하여 문법 오류를 컴파일 시점에 + 코드 재사용
동적 쿼리 문제 해결
쉬운 SQL식 문법
프로젝트 환경설정
프로젝트 생성
Querydsl을 사용할 때는 프로젝트 생성시 이전과 다른 설정 존재
스프링 부트에서 Querydsl의 버전 관리는 해주는데 starter의 dependencies에는 없다.
Dependencies
-Spring Web
-Spring Data JPA
-Lombok
-H2 database
프로젝트 기본 설정
1. Gradle - Build and Run - IntelliJ IDEA
2. Annotation processors 옵션 on (Lombok)
Querydsl 설정
build.gradle
1. 플러그인, 라이브러리 추가
2. Querydsl Q파일 생성 디렉토리 설정 추가
-엔티티 클래스 정의 후 compileQuerydsl 실행하여 Q파일 생성 확인

설정 후 gradle 컴파일 실행 시 Q파일을 생성하는 프로세서도 동작한다.
주의: Q파일은 git 리포지토리에서 관리하면 안된다. 시스템이 자동으로 생성해주는 것이기 때문에 내려 받은 환경에선 변경될 요지 존재. (기본적으로 빌드 폴더는 외부 리포지토리 동기화 옵션에 ignore 설정 포함)
기본 문법
시작 - JPQL vs Querydsl
Querydsl은 JPQL 빌더. 결국 JPQL로 번역됨
JPQL: 문자(실행 시점 오류), Querydsl: 코드(컴파일 시점 오류)
JPQL: 파라미터 바인딩 직접, Querydsl: 파라미터 바인딩 자동 처리
*Querydsl은 Prepare statement의 파라미터 바인딩 방식을 사용하여 SQL 인젝션 공격으로부터 안전
JPAQueryFactory를 필드로 제공하면 동시성 문제는 어떻게 될까?
동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager(em)에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱정하지 않아도 된다.
기본 Q-Type 활용
Q클래스 인스턴스를 사용하는 2가지 방법
QMember qMember = new QMember("m"); //별칭 직접 지정
QMember qMember = QMember.member; //기본 인스턴스 사용.
QMember를 static import하면 코드를 깔끔하게 유지 가능 (권장)
참고: 지정한 별칭으로 jpql이 생성됨. 같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하자.
참고: 다음 설정을 추가하면 실행되는 JPQL을 볼 수 있다.
spring.jpa.properties.hibernate.use_sql_comments: true
검색 조건 쿼리
.select() + .from() => .selectFrom()
jpql: where member.username == "member1" and member.age == 10
querydsl: .where(member.username.eq("member1").and(member.age.eq(10)))
JPQL이 제공하는 모든 검색 조건 제공
<비교 표현식>
A.goe(B) : greater or equal (A >= B)
A.gt(B) : greater than (A > B)
A.loe(B) : less or equal (A <= B)
A.lt(B) : less than (A < B)
.where()는 파라미터로 Predicate...을 허용하기 때문에 .and() 대신 o1, o2, ... 로 복수의 조건을 넘길 수 있다.
이 경우 null 값 무시 -> 메서드 추출을 활용해서 동적 쿼리를 깔끔하게 만들 수 있음 (뒤에서 설명)
결과 조회
fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne() : 단 건 조회
-결과가 없으면 : null
-결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행 (deprecated)
fetchCount() : count 쿼리로 변경해서 count 수 조회. (deprecated)
참고: 복잡한 쿼리에서는 count 쿼리가 잘 동작하지 않아서 deprecated. 따로 count 쿼리를 작성하자.
정렬(Sort)
.orderBy(정렬 조건1, 정렬 조건2, ...)
desc() , asc() : 일반 정렬
nullsLast() , nullsFirst() : null 데이터 순서 부여
페이징
페이징 결과 검증을 위해 정렬이 선행되어야 한다.
.offset()
.limit()