본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.




패스트캠퍼스의 '9개 도메인 프로젝트로 끝내는 백엔드 웹 개발 (Java/Spring) 초격차 패키지 Online' 강의 수강 16일차!
오늘은 커뮤니티 피드 서비스 프로젝트에 Spring Data JPA, JPQL, QueryDSL을 이용한 쿼리 작성 작업을 진행하였다.
public interface JpaUserRepository extends JpaRepository<UserEntity, Long> {
}
이렇게 JpaRepository를 상속받으면 JPA에서 제공하는 쿼리 메소드들을 사용할 수 있다.
JPA 쿼리 메소드는 메소드 명명규칙에 따라 이름만 지으면 따로 쿼리를 작성할 필요가 없다는 것이 장점이지만, 성능이 중요하거나 복잡한 조건이 포함된 쿼리의 경우는 구현하기 어렵다. 이럴 때 JPQL과 QueryDSL을 사용한다.
JPQL이란 SQL을 기반으로 한 객체모델용 쿼리 언어로, 데이터베이스가 아닌 엔티티 객체 대상으로 쿼리하는 것이다.
@Query(value = "SELECT new org.fastcampus.user.application.dto.GetUserListResponseDto(u.name, u.profileImage) "
+ "FROM UserRelationEntity ur "
+ "INNER JOIN UserEntity u ON ur.followingUserId = u.id "
+ "WHERE ur.followerUserId = :userId")
List<GetUserListResponseDto> getFollowerUserList(Long userId);
특정 사용자가 팔로우하고 있는 유저의 정보들을 조회하는 쿼리다. 쿼리를 보면 알 수 있듯이, 테이블명이 아닌 객체를 대상으로 작성되어 있다.
new 키워드를 통해 조회 결과를 DTO로 매핑할 수 있다.
만약 특정 데이터베이스에서만 제공하는 함수를 사용해야 하는 경우, 쿼리에 nativeQuery = true 옵션을 주면 된다.
다음은 QueryDSL을 사용하여 쿼리를 작성해보았다.
QueryDSL이란, 쿼리문을 코드로 작성할 수 있게 해주는 프레임워크이다.
프로젝트가 컴파일될 때 @Entity 어노테이션을 검색하여 QClass를 생성한다.
쿼리를 작성하기 위해 빌드 파일에 QueryDSL 관련 의존성과 설정을 추가해줬다.
그런데, 설정한 경로에 Q클래스들이 생성은 잘 되지만 다른 클래스에서 import되지 않는 문제가 발생하였다.
/* 기존
val querydslDir = "${layout.projectDirectory}/build/generated/querydsl"
*/
val querydslDir = layout.projectDirectory.dir("build/generated/querydsl")
sourceSets {
getByName("main").java.srcDirs(querydslDir)
}
tasks.withType<JavaCompile> {
options.generatedSourceOutputDirectory.set(file(querydslDir))
}
tasks.named("clean") {
doLast {
file(querydslDir).deleteRecursively()
}
}
기존의 방법은 디렉토리 경로를 문자열 형태로 지정한 것이고, 해결 방법은 디렉토리 경로를 Directory 객체로 반환하여 사용한 것이다.
기존의 방법이 왜 sourceSet 경로에 등록되지 않은 건지는 아직 잘 모르겠다... 좀 더 알아보고 추후에 글로 정리해봐야겠다.
여튼, 작은 문제를 해결하고 QueryDSL을 이용하여 쿼리를 작성해보았다!
public List<GetUserListResponseDto> getFollowerList(Long userId, Long lastFollowerId) {
return jpaQueryFactory
.select(
Projections.fields(
GetUserListResponseDto.class
)
)
.from(relation)
.join(user).on(relation.followingUserId.eq(user.id))
.where(
relation.followerUserId.eq(userId),
hasLastData(lastFollowerId)
)
.orderBy(user.id.desc())
.limit(20)
.fetch();
}
private BooleanExpression hasLastData(Long lastId) {
if(lastId == null) {
return null;
}
return user.id.lt(lastId);
}
JPAQueryFactory라는 EntityManager 기반으로 작동하는 쿼리 빌더를 사용한다.
Projections.fields()를 사용하여 조회 결과를 DTO로 매핑한다.
메소드명이 SQL의 구문명과 같고, 체이닝이 가능해서 코드의 가독성이 굉장히 좋은 것 같다.
메소드를 만들어 손쉽게 페이징 기능도 적용시킬 수 있는 것이 정말 놀라웠다.
JPQL은 사용해봤지만 QueryDSL은 처음이었는데, 확실히 동적쿼리를 작성할 때 정말 편리하다는 장점이 있는 것 같다.