인턴

JPQL, QueryDSL이란 뭘까??

jonghyeon6084 2025. 7. 11. 10:21
728x90

과제를 개발의 요구사항 중 JPA가 포함되어 있었고, 페이징 처리를 하는 과정에서 한 고민을 하게 된 부분이 있다.

바로 한 컬럼만 빼고 나머지 컬럼을 조회하는 것이었는데, 굳이 사용자에게 비밀번호까지 알려줄 필요는 없기 때문이다. 근데 JPA에서 제공하는 메서드로는 한 컬럼을 제외하고 나머지 컬럼을 가져오는 게 어렵다고 판단했다.

사실 같이 일하고 있는 인턴분에게도 위 사항에 대해 의논해봤는데 findAll() 사용한 다음 dto에 비밀번호만 빼고 가져오면 되지 않냐라는 얘기가 나왔지만, 이건 보안상의 문제로 의도치 않게 보여질 수 있으므로, 애초에 DB에서 가지고 올 때 패스워드를 빼고 가지고 오는 게 맞다는 생각에 방법을 찾아보다가 JPQLQueryDSL를 알게 되었다.

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을 사용하면 될 거 같다.