CS/Spring
[Spring/Springboot] QueryDSL이란?
hhongyeahh
2024. 12. 22. 07:09
QueryDSL
- 문자가 아닌 자바코드로 JPQL을 작성할 수 있음
- JPQL 빌더 역할
- 컴파일 시점에 문법 오류를 찾을 수 있음
- 동적 쿼리 작성 편리함
- 단순하고 쉬움
- 실무 사용 권장
JPAFactoryQuery query = new JPAQueryFactory(em);
QMember m = QMember.member;
List<Member> list = query.selectFrom(m)
.where(m.age.get(18))
.orderBy(m.name.desc())
.fetch();
Querydsl의 개념과 JPQL과의 차이점
- Querydsl은 타입 안전한 쿼리 작성을 도와주는 도구로, 쿼리를 마치 메서드를 호출하듯이 Java 코드로 작성가능
- Querydsl은 쿼리의 동적 생성이 필요할 때 특히 유용하며, JPQL을 사용할 때 발생할 수 있는 타입 불안정성 문제를 해결
- JPQL
- 문자열로 쿼리를 작성하기 때문에, 오타나 필드 변경 등이 있을 때 컴파일 시점에 오류를 발견할 수 없습니다
- 런타임에만 오류가 발생할 수 있어 유지보수에 어려움이 있습니다.
- Querydsl
- Java 코드로 쿼리를 작성하여 컴파일 시점에 오류를 잡을 수 있으며, 쿼리를 동적으로 작성할 수 있어 복잡한 조건을 쉽게 추가할 수 있습니다.
QClass란?
- Querydsl은 QClass라는 클래스를 자동으로 생성하여 엔티티에 대응하는 쿼리 객체를 제공합니다.
- QClass는 Querydsl이 제공하는 도구로, 쿼리를 작성할 때 엔티티 필드에 직접 접근할 수 있도록 도와주는 클래스입니다.
- 예를 들어 QReview는 Review 엔티티의 각 필드를 Java 코드처럼 접근할 수 있게 해줍니다.
- QReview.review는 Review 엔티티와 연관된 Querydsl QClass 객체로, 이 객체를 통해 Review 엔티티의 필드들에 접근할 수 있습니다.
QClass 사용의 장점
- 타입 안전성: 엔티티 필드를 QReview.review.name과 같이 Java 필드 접근 방식으로 사용하여, 컴파일 시점에 필드 오타나 타입 오류를 방지할 수 있습니다.
- 유지보수 용이: 엔티티 필드 변경이 있을 때 자동으로 QClass도 업데이트되기 때문에, Querydsl 쿼리에서 필드명을 자동으로 반영할 수 있습니다.
@Repository
@RequiredArgsConstructor
public class ReviewRepositoryImpl implements ReviewRepositoryCustom {
private final JPQLQueryFactory jpqlQueryFactory;
private final QReview review = QReview.review;
private final QStore store = QStore.store;
private final QMember member = QMember.member;
@Override
public Page<Review> dynamicQueryWithBooleanBuilder(String storeName, Pageable pageable) {
BooleanBuilder predicate = new BooleanBuilder();
if (storeName != null) {
predicate.and(review.store.name.eq(storeName));
}
// Fetch Join으로 리뷰와 스토어, 멤버를 함께 로딩
List<Review> reviews = jpqlQueryFactory
.selectFrom(review)
.join(review.store, store).fetchJoin() // 스토어를 Fetch Join
.join(review.member, member).fetchJoin() // 멤버를 Fetch Join
.where(predicate)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
// 전체 데이터 수 조회 (이 때는 Fetch Join을 사용하지 않아도 됨)
long total = jpqlQueryFactory
.selectFrom(review)
.join(review.store, store)
.where(predicate)
.fetchCount();
return new PageImpl<>(reviews, pageable, total);
}
}
BooleanBuilder
- 예를 들어, 리뷰(Review) 엔티티를 검색할 때 storeName, memberName, score라는 세 가지 조건을 기준으로 검색할 수 있다고 가정
- 사용자가 세 조건 중 일부만 입력할 수도 있고, 모두 입력 가능
- 이럴 때 BooleanBuilder를 사용하여 입력된 조건만 반영하는 동적 쿼리를 구성가능
BooleanBuilder를 사용한 동적 쿼리 코드
java
코드 복사
public List<Review> searchReviews(String storeName, String memberName, Double minScore) {
BooleanBuilder predicate = new BooleanBuilder(); // 조건을 담는 BooleanBuilder 생성
// 조건 1: storeName이 있을 경우 조건에 추가
if (storeName != null) {
predicate.and(review.store.name.eq(storeName));
}
// 조건 2: memberName이 있을 경우 조건에 추가
if (memberName != null) {
predicate.and(review.member.name.eq(memberName));
}
// 조건 3: minScore가 있을 경우 조건에 추가
if (minScore != null) {
predicate.and(review.score.goe(minScore)); // 리뷰의 점수가 minScore 이상일 경우
}
// Querydsl을 사용해 동적 쿼리 실행
return jpqlQueryFactory
.selectFrom(review)
.where(predicate) // BooleanBuilder를 where 절에 추가
.fetch();
}
- BooleanBuilder 생성: BooleanBuilder predicate = new BooleanBuilder();로 기본 조건을 담을 빈 BooleanBuilder 객체를 생성합니다.
- 조건 추가:
- storeName 조건: storeName이 null이 아니면, review.store.name.eq(storeName) 조건을 predicate에 and로 추가합니다.
- memberName 조건: memberName이 null이 아니면, review.member.name.eq(memberName) 조건을 추가합니다.
- minScore 조건: minScore가 null이 아니면, review.score.goe(minScore) 조건을 추가하여 최소 점수 이상인 리뷰를 검색합니다.
- 조건 적용: 최종적으로 where(predicate)를 사용하여 모든 조건이 적용된 BooleanBuilder를 where 절에 추가합니다.
이렇게 하면 사용자가 입력한 조건에 맞는 리뷰만 조회하는 유연한 쿼리를 작성할 수 있습니다.
BooleanBuilder의 결과
- 모든 조건이 제공된 경우:
SELECT * FROM Review r
JOIN Store s ON r.store_id = s.id
JOIN Member m ON r.member_id = m.id
WHERE s.name = '요아정' AND m.name = '이다혜' AND r.score >= 4.0;
- 일부 조건만 제공된 경우 (storeName만 입력됨)
SELECT * FROM Review r
JOIN Store s ON r.store_id = s.id
WHERE s.name = '요아정';
- 이처럼 BooleanBuilder는 동적 조건을 손쉽게 추가하고 제거하여 상황에 맞는 쿼리를 생성하는 데 중요한 역할을 합니다. 이를 통해 사용자의 다양한 입력 조건에 맞게 쿼리를 유연하게 구성할 수 있음