
QueryDSL?
Q 클래스 파일들을 이용하여
SQL 과 유사하게 쿼리를 작성하여 데이터 처리를 돕는 프레임워크
이제 JPA 를 처음 사용해보기도 했는데, 생각보다 쿼리 자체를 끌어와서 쓰기엔
복잡한 부분도 있었고, 만약 JPA 환경이 아니면 뭘 쓰는게 나을까를 알아보다가
JOOQ 라는 것을 알게 되었고, 동적쿼리 활용은 QueryDSL도 겸사겸사 알게 되었다
우선 데이터는 MySQL의 Country 예제를 가져왔다
gradle 선언
우선 기존 QueryDSL 은 취약점이 있기도 하고, 전체적으로 프로젝트가
멈춘거 같아 좀 알아보고 들어보고 하다 OpenFeign 측이 fork 하여
계속 이어나가는 거 같아 OpenFeign QueryDSL 을 실습하려한다
현재 버전은 7.1
자세한 링크는 https://github.com/OpenFeign/querydsl
implementation 'io.github.openfeign.querydsl:querydsl-core:7.1'
implementation 'io.github.openfeign.querydsl:querydsl-jpa:7.1'
annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:7.1:jpa'
// Q 클래스 생성 설정을 따로 관리할 경우 (설정 안할시 build 내에 첨부됨)
def generated = 'src/main/generated'
sourceSets {
main.java.srcDirs += [generated]
}
tasks.withType(JavaCompile).configureEach {
options.generatedSourceOutputDirectory = file(generated)
}
clean {
delete file(generated)
}후에 그래들 빌드 후 기존 빌드를 클린하고 컴파일 자바를 해보니
Q 클래스 파일들이 생성된 걸 확인했다
Q class?
Entity를 QueryDSL에서 활용할 때 타입 관련하여 안전한 쿼리 작성을 위해
QueryDSL이 생성하는 클래스 파일들
JPAQueryFactory 등록하기
다음으로는 JPAQueryFactory Bean 을 등록했다
Repository 단에서 주입받아 사용하기 위해 선언
QueryDSL 쿼리를 만들기 위한 시작점이 되는 것이 JPAQueryFactory 이다
QueryDSL 을 활용할 때 EntityManager 의 직접 관리가 상당히 번거롭기 때문에
(코드의 생성이 매번 있어야함) 내부에 우선 선언하고 주입하여 사용한다또, Spring 의 트랜잭션 관리와 연동하기 위해서 등록해야한다
이제 사용을 위해 Repository 를 작성하는데,
보통 Impl 파일로 구현을 추상한다 (DIP)
// queryDsl 기본 구조
queryFactory
.select(조회대상)
.from(대상엔티티)
.join(대상엔티티.연관 필드, 연관된 엔티티의 Q 클래스)[.fetchJoin()]
.where(조건)
.orderBy(정렬)
.fetch();
// 연관 데이터의 데이터도 필요하면? fetchJoin// Repository interface & Implement
public interface PostRepository {
List<PostSummaryDto> findPostSummary(String username);
}
@Repository
@RequiredArgsConstructor
@Transactional
public class PostRepositoryImpl implements PostRepository {
private final JPAQueryFactory queryFactory;
@Override
public List<PostDto> findPostSummary(String username) {
return queryFactory
.select(Projections.constructor(
PostDto.class
, post.content
, comment.countDistinct().intValue()
)).from(post)
.leftJoin(post.comments, comment)
.where(post.user.username.eq(username))
.groupBy(post.id)
.fetch();
}
}우선 일반적인 전체 조회를 구성해서 response dto 에 담았다
내부에서 우선 Bean 으로 선언했던 JPAQueryFactory 를 주입한 후에
이를 빌더 패턴으로 작성하여 실행하는 타입이다
Projection 패턴
Record 클래스 + Constructor 생성 패턴이 제일 효율적으로 보인다
// Projections.bean - setter로 매핑
queryFactory
.select(Projections.bean(PostDto.class,
post.id,
post.title
))
.from(post)
.fetch();
// Projections.fields - 필드에 직접 매핑 (setter 없어도 됨)
queryFactory
.select(Projections.fields(PostDto.class,
post.id,
post.title
))
.from(post)
.fetch();
// Projections.constructor * - 생성자로 매핑
// 만약 DTO가 정적 팩토리 패턴이면 프로젝션 할수 없음 유의
// record + Projections.constructor
queryFactory
.select(Projections.constructor(PostDto.class,
post.id,
post.title
))
.from(post)
.fetch();
// @QueryProjection * (하지만 DTO가 queryDSL 의존이 됨)
// 컴파일 시점에 체킹이 됨
// DTO 생성자에 어노테이션 추가
class PostDto {
@QueryProjection
public PostDto(Long id, String title) { ... }
}
queryFactory
.select(new QPostDto(post.id, post.title))
.from(post)
.fetch();