
→ 그래서 사용하는 게 QueryDSL이다.
참고 : gradle 버전이나 IntelliJ 버전에 따라 QueryDSL 설정이 상이하기 때문에 다른 환경에서는 동작하지 않을 수도 있다.
build.gradle
빨간색 글씨에는 QueryDSL 버전이 들어간다.
dependencies {
// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0'
annotationProcessor(
"javax.persistence:javax.persistence-api",
"javax.annotation:javax.annotation-api",
'com.querydsl:querydsl-apt:5.0.0:jpa')
}
// QueryDSL
sourceSets {
main {
java {
srcDirs = ["$projectDir/src/main/java", "$projectDir/build/generated"]
}
}
}
JPAAnnotationProcessor 를 사용해 Q클래스를 생성한다.querydsl-apt가 @Entity와 @Id 등의 어노테이션을 알 수 있도록, javax.persistence 와 javax.annotation 을 annotationProcessor에 함께 추가한다.annotationProcessor는 Java 컴파일러 플러그인으로써, 컴파일 단계에서 어노테이션을 분석하여 추가적인 파일을 생성할 수 있다.

$projectDir/build/generated 디렉토리 하위에 Entity로 등록된 클래스의 이름앞에 Q가 붙은 형태로 Q클래스가 생성된 것을 볼 수 있다.JPQL로 작성하는 경우
@DisplayName("hi 내용을 포함하며 댓글이 1개 이상인 Post를 조회한다.")
@Test
void jpa_findPostsByMyCriteria_Three() {
EntityManager entityManager = testEntityManager.getEntityManager();
List<Post> posts = entityManager.createQuery("select p from Post p where p.content like '%hi%' and p.comments.size > 0 order by p.id desc", Post.class)
.getResultList();
assertThat(posts).hasSize(3);
}
QueryDSL로 작성하는 경우
@DisplayName("hi 내용을 포함하며 댓글이 1개 이상인 Post를 ID 내림차순으로 조회한다.")
@Test
void queryDsl_findPostsByMyCriteria_Three() {
EntityManager entityManager = testEntityManager.getEntityManager();
JPAQuery<Post> query = new JPAQuery<>(entityManager);
QPost qPost = new QPost("p");
List<Post> posts = query.from(qPost)
.where(qPost.content.contains("hi")
.and(qPost.comments.size().gt(0))
).orderBy(qPost.id.desc())
.fetch();
assertThat(posts).hasSize(3);
}
@DisplayName("QueryDsl을 통해 Post 조회시 Comment를 Fetch Join한다.")
@Test
void queryDsl_FetchJoinComments_Success() {
EntityManager entityManager = testEntityManager.getEntityManager();
JPAQuery<Post> query = new JPAQuery<>(entityManager);
QPost qPost = new QPost("p");
QComment qComment = new QComment("c");
List<Post> posts = query.distinct()
.from(qPost)
.leftJoin(qPost.comments, qComment).fetchJoin()
.fetch();
assertThat(posts).hasSize(3);
}다음의 JPQL을 바꿔보도록 하자
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("select p from Post p join fetch p.comments")
List<Post> findAllInnerFetchJoin();
@Query("select distinct p from Post p join fetch p.comments")
List<Post> findAllInnerFetchJoinWithDistinct();
//...
}
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
public interface PostCustomRepository {
List<Post> findAllInnerFetchJoin();
List<Post> findAllInnerFetchJoinWithDistinct();
}
PostCustomRepository를 생성하여 여기에 정의한다.import static com.learning.jpa.domain.post.QPost.post;
@Repository
public class PostCustomRepositoryImpl implements PostCustomRepository {
private final JPAQueryFactory jpaQueryFactory;
public PostCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
}
@Override
public List<Post> findAllInnerFetchJoin() {
return jpaQueryFactory.selectFrom(post)
.innerJoin(post.comments)
.fetchJoin()
.fetch();
}
@Override
public List<Post> findAllInnerFetchJoinWithDistinct() {
return jpaQueryFactory.selectFrom(post)
.distinct()
.innerJoin(post.comments)
.fetchJoin()
.fetch();
}
}
PostCustomRepository 를 구현하는 PostCustomRepositoryImpl 클래스에 QueryDSL 쿼리를 작성한다.PostCustomRepository 인터페이스도 상속하게 하자@Repository
public class QueryRepository {
private final JPAQueryFactory jpaQueryFactory;
public QueryRepository(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
}
public List<Post> findAllPostsInnerFetchJoin() {
return jpaQueryFactory.selectFrom(post)
.innerJoin(post.comments)
.fetchJoin()
.fetch();
}
public List<Orphan> findALlOrphans() {
return jpaQueryFactory.selectFrom(orphan)
.distinct()
.leftJoin(orphan.parent).fetchJoin()
.where(orphan.name.contains("abc"))
.fetch();
}
}