*공식문서 http://querydsl.com/static/querydsl/3.6.3/reference/ko-KR/html_single/#d0e265
Querydsl - 레퍼런스 문서
본 절에서는 SQL 모듈의 쿼라 타입 생성과 쿼리 기능을 설명한다. com.mysema.query.sql.Configuration 클래스를 이용해서 설정하며, Configuration 클래스는 생성자 인자로 Querydsl SQL Dialect를 취한다. 예를 들어
querydsl.com
JPA를 활용한 웹 프로젝트 진행 중, 복잡한 쿼리 요구 사항을 효율적으로 처리하기 위해 QueryDSL을 사용하는 방법을 정리해보았다.
💡 QueryDSL은 타입 안정성을 보장하는 Java 기반의 동적 쿼리 라이브러리로 SQL, JPQL, HQL, MongoDB, JPA 등과 같은 다양한 데이터베이스 쿼리를 Java 코드로 안전하게 작성할 수 있게 도와준다.
✔️ 특징
1) 타입 안전성
쿼리를 문자열로 작성하지 않고, 엔티티의 필드와 메서드를 Java 코드로 작성하므로 컴파일 타임에 오류를 확인할 수 있다. 이는 런타임에서 발생할 수 있는 쿼리 오류를 방지해주고, 코드 작성 시 IDE의 자동 완성 기능을 활용할 수 있다. (코드 기반으로 쿼리를 작성하므로 SQL과 같은 텍스트 기반 커리보다 유지보수가 쉽다)
QProduct product = QProduct.product;
List<Product> result = queryFactory
.selectFrom(product)
.where(product.price.gt(1000))
.fetch();
2) 동적 쿼리 지원
여러 조건을 BooleanBuilder를 통해 조합하여 복잡한 동적 쿼리를 간편하게 만들 수 있다. 조건을 추가하거나 제거할 때 null 체크나 if 문으로 쉽게 처리할 수 있어 동적인 상황에서 유리하다.
BooleanBuilder builder = new BooleanBuilder();
if (price != null) {
builder.and(product.price.gt(price));
}
if (category != null) {
builder.and(product.category.eq(category));
}
List<Product> results = queryFactory
.selectFrom(product)
.where(builder)
.fetch();
📍 동적인 상황
변경되거나 상황에 따라 조건이 달라지는 상황으로 고정된 쿼리가 아닌 사용자의 입력이나 특정 조건에 따라 쿼리의 조건이 달라지는 경우
3) 복잡한 쿼리 작성에 유리
복잡한 조인, 서브쿼리, 집계 함수 등을 쉽게 처리할 수 있다. 특히 복잡한 엔티티 간의 관계를 처리하는 경우에 유리하며, 쿼리 최적화 및 튜닝을 위해 유연한 조건식을 사용할 수 있다.
QProduct product = QProduct.product;
QOrder order = QOrder.order;
List<Product> result = queryFactory
.selectFrom(product)
.where(product.id.in(
JPAExpressions.select(order.product.id)
.from(order)
.where(order.totalAmount.gt(1000))
))
.fetch();
✔️ 기술 용어
1) QuerydslPredicateExecutor 인터페이스
QueryDSL을 이용해 Predicate 기반의 동적 쿼리를 실행할 수 있게 지원하는 인터페이스. Prediate는 QueryDSL의 조건문을 담는 객체이다. 예를 들어, 사용자가 검색 필터를 통해 다양한 조건을 선택할 때, Predicate를 이용해 조건을 동적으로 만들어낼 수 있다. 단, 기본적인 쿼리를 쉽게 처리할 수 있도록 제공되는 인터페이스이므로 복잡한 쿼리에는 적합하지 않다. (복잡한 쿼리는 커스텀 리포지토리를 사용하는 것이 적합하다)
findOne(Predicate predicate) | 주어진 Predicate에 맞는 단일 엔티티를 반환 |
findAll(Predicate predicate) | 주어진 Predicate에 맞는 모든 엔티티를 반환 |
findAll(Predicate predicate, Sort sort) | 주어진 Predicate와 Sort를 사용하여 정렬된 엔티티를 반환 |
count(Predicate predicate) | 주어진 Predicate에 맞는 엔티티의 수를 반환 |
exists(Predicate predicate) | 주어진 Predicate에 맞는 엔티티가 존재하는지 확인 |
findAll(Predicate predicate, Pageable pageable) | 주어진 Predicate와 Pageable을 사용하여 페이징된 엔티티를 반환 |
2) BooleanBuilder
QueryDSL에서 조건을 동적으로 구성할 때 사용하는 클래스로 여러 조건을 조합할 수 있기 때문에 복잡한 쿼리를 동적으로 만들어야 할 때 유용하다. Predicate 타입의 조건을 여러 개 연결할 수 있다.
QProduct product = QProduct.product;
BooleanBuilder builder = new BooleanBuilder();
// BooleanBuilder를 사용한 동적 조건 추가
// and(), or()을 사용해서 where절을 구성할 수 있음
if (searchType.equals("writer")) {
builder.and(product.writer.like("%" + searchName + "%")); // writer 조건 추가
}
if (searchType.equals("title")) {
builder.and(product.title.like("%" + searchName + "%")); // title 조건 추가
}
// 최종 쿼리 실행
List<Product> results = queryFactory
.selectFrom(product)
.where(builder) // BooleanBuilder가 포함된 동적 where 조건
.fetch();
✔️ 문법
예시를 보면서 QueryDSL의 기본 문법을 익혀보자.
(*자세한 문법은 상단의 공식문서를 참고하자)
1) 기본 문법
@Override
public Memo dslSelect() {
// Q클래스를 사용
QMemo memo = QMemo.memo;
Memo m = jpaQueryFactory.select(memo) // Q클래스 memo의 전체 컬럼 조회
.from(memo)
.where(memo.mno.eq(10L) ) // 멤버 변수로 조건
.orderBy(memo.text.desc()) // text를 기준으로 한 내림차순 정렬
.fetchOne(); // fetchOne() 1행 조회, fetch() 리스트 조회, execute() 는 insert, update, delete
return m;
}
select
가져올 데이터를 지정한다. Entity의 모든 데이터를 읽거나(예시 참고) 클래스 접근자를 사용하여 Entity 내의 일부 정보만 가져올 수 있다. (ex. memo.mno)
from
데이터를 조회할 테이블을 지정한다.
where
어떤 조건으로 검색할지 지정한다. 파라미터를 여러 개 전달할 경우 자동으로 AND 연산을 적용한다. AND연산, OR 연산을 사용할 수 있다.
order by
정렬 조건을 지정한다.
fetch /execute
단일행을 조회할 경우 fetchOne(), 리스트를 조회할 경우 fetch(), insert / update / delete인 경우 execute()를 사용한다.
offset, lilmit
페이지 처리에 사용한다.
2) 조인
Q클래스 파일은 원본의 Entity 멤버들을 똑같이 가지고 있다. 두 대상을 조인할 때는 ID를 기준으로 조인한다. 참조 관계가 없는 타입을 조인할 때는 on절로 직접 명시할 수 있다.
@Override
public List<Memo> dslJoin() {
QMemo memo = QMemo.memo;
QMember member =QMember.member;
List<Memo> m = jpaQueryFactory.select(memo)
.from(memo)
//.join(memo.member, member) // entityA.entitiyB, entityB
//.leftJoin(memo.member, member)
//.rightJoin(memo.member, member)
.join(memo.member, member).on(memo.writer.eq(member.name))
.fetch();
return m;
}
💡조인 결과를 DTO로 반환 받기
1. 튜플로 반환받기
Tuple tuple = jpaQueryFactory
.select(memo.mno, memo.text) // Q클래스.멤버변수
.from(memo)
.where(memo.mno.eq(10L))
.orderBy(memo.text.desc()) // 내림차순 정렬
.fetchOne();
System.out.println(tuple.get(memo.mno));
System.out.println(tuple.get(memo.text));
🟡 실제 적용 예시
1. 먼저 build.gradle에 QueryDSL 사용을 위한 의존성을 추가해주었다. (프로젝트 버전 확인 필수)
plugins {
...
id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' // Querydsl 플러그인 추가
}
configurations {
...
querydsl.extendsFrom compileClasspath // Querydsl 설정 추가
}
...
dependencies {
...
implementation 'com.querydsl:querydsl-jpa:5.0.0' // Querydsl 의존성
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa' // Querydsl 의존성
}
// Querydsl 설정 추가
sourceSets {
main {
java {
srcDirs += 'build/generated/sources/annotationProcessor/java/main'
}
}
}
의존성 추가 후 gradle을 업데이트하면 @Entity 클래스마다 동일한 패키지에 Q클래스가 자동 생성된다. Q클래스는 다음과 같이 QueryDSL에서 사용 가능하다.
QProduct product = QProduct.product;
QProduct product = new QProduct("myProduct");
2. 다음으로 CustomRepository 인터페이스와 CustomRepositoryImpl 클래스를 생성하고 리포지토리에 해당 CustomRepository와 QuerydlPredicateExecutor<T>를 추가로 상속시켜주었다.
@Repository
public interface ProductRepository extends JpaRepository<Product, String>, ProductRepositoryCustom, QuerydslPredicateExecutor<Product> {
...
}
'☕ Java' 카테고리의 다른 글
[Java] 자바 메서드(Method) 총정리 (0) | 2024.09.22 |
---|---|
[Java] 자바 기초 문법 (0) | 2024.09.22 |