JPQL, QueryDSL이란 뭘까??
과제를 개발의 요구사항 중 JPA가 포함되어 있었고, 페이징 처리를 하는 과정에서 한 고민을 하게 된 부분이 있다.
바로 한 컬럼만 빼고 나머지 컬럼을 조회하는 것이었는데, 굳이 사용자에게 비밀번호까지 알려줄 필요는 없기 때문이다. 근데 JPA에서 제공하는 메서드로는 한 컬럼을 제외하고 나머지 컬럼을 가져오는 게 어렵다고 판단했다.
사실 같이 일하고 있는 인턴분에게도 위 사항에 대해 의논해봤는데 findAll() 사용한 다음 dto에 비밀번호만 빼고 가져오면 되지 않냐라는 얘기가 나왔지만, 이건 보안상의 문제로 의도치 않게 보여질 수 있으므로, 애초에 DB에서 가지고 올 때 패스워드를 빼고 가지고 오는 게 맞다는 생각에 방법을 찾아보다가 JPQL과 QueryDSL를 알게 되었다.
1. JPQL
JPQL은 JPA의 일부러 Query를 table
이 아닌 객체(entity)
기준으로 작성하는 __객체지향 쿼리 언어 라고 정의할 수 있다. JPQL은 객체
를 기준으로 모든 것이 움직이기 때문에 개발할 때, Table이 매핑되는 객체
가 반드시 존재해야 하며 당연하게도 검색할 때도 TABLE
이 아닌 객체
를 대상으로 검색해야 한다. 전반적인 특징에 대해서 간단히 정리해보자.
1-1. JPQL 특징
1. SQL을 추상화한 JPA의 객체지향 쿼리
2. table이 아닌 Entity 객체 를 대상으로 개발
3. Entity와 속성은 대소문자 구분(Person <> person)
4. 별칭(alias) 사용 필수
1-2. repository interface 활용
@Query("select new com.example.assignment.domain.dto.user.UserListDto(
u.id, u.name, u.level, u.desc, u.reg_date) from t_user u")
Page<UserListDto> findAllExceptPwd(Pageable pageable);
하지만 여기서의 문제점을 생각해보자면 쿼리가 String 형태로 작성되고 있다는 점이다.
정리하자면
JPQL의 문제점
1. JPQL은 String 형태이기 때문에 개발자 의존적 형태
2. Complie 단계에서 Type-Check가 불가능
3. RunTime 단계에서 오류 발견 가능(장애 risk 상승)
2. QueryDSL
위 부분을 보완하기 위해 나온 것이 query DSL이다.
정적 타입을 이용해서 SQL, JPQL을 코드로 작성할 수 있도록 도와주는 오픈소스 빌더 API
query DSL을 사용하는 목적은 뚜렷하다.
기존 방식(mybatis, JPQL, 등등)은 모두 String 형태로 쿼리가 작성되었고 이로 인해 Complie 단계에서 Type-Check가 불가했다.
이러한 risk를 줄이기 위해 query DSL 이 등장했고 이를 통해 Complie 단계에서 Type-Check가 가능해진 것이다.
예시를 보자.
먼저 UserListDto를 만들어 놓고 나서
public Page<UserListDto> findAllExceptPwd(Pageable pageable) {
QUser user = QUser.user;
List<UserListDto> content = queryFactory
.select(Projections.constructor(UserListDto.class,
user.id,
user.name,
user.level,
user.desc,
user.regDate))
.from(user)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
long total = queryFactory
.select(user.count())
.from(user)
.fetchOne();
return new PageImpl<>(content, pageable, total);
}
이렇게 작성했을 때 궁금한 부분이 2가지가 있다.
QUser란?
- QueryDSL에서 엔티티별로 자동 생성되는 클래스
- “Q + 엔티티명”으로 만들어짐 (예: User → QUser)
- 이 객체를 이용해 타입 안전하게 쿼리를 작성할 수 있음
Projections.constructor란?
- Projections.constructor는
지정한 클래스의 생성자를 호출해서 객체(DTO)를 만들어 반환하는 QueryDSL의 메서드 - 보통 DB 엔티티 전체를 반환하는 게 아니라,
일부 컬럼만 뽑아서 DTO로 직접 조회할 때 사용
이제 궁금한 부분을 넘기고,
JPQL과의 차이를 보자면
queryDSL은 모든 쿼리에 대한 내용이 함수 형태로 제공된다.
이렇게 엔티티 + 함수 형태로 구성된 queryDSL을 통해 구현된 코드는 오류가 존재할 시, complie 단계에서 바로 확인이 가능하며 이에 따른 후속 조치가 가능하기에 그만큼 risk가 줄어든다.
뭐 물론 너무 코드가 길어지는 단점도 있다.
너무 짧은 코드라면 JPQL을 사용해도 괜찮을 거 같고,
"아니다 ! 나는 무조건 오류가 나면 컴파일 단계에서 check하고 싶다 !"
라고 한다면 queryDSL을 사용하면 될 거 같다.