자바-백엔드

QueryDSL은 왜 쓰는걸까?

게로망 2026. 2. 6. 17:58

실무에서 백엔드 개발을 하던 중 마이바티스 대신 QueryDSL를 도입한 상황이 있었다

여기서 QueryDSL는 왜 사용되는걸까? 라는 의문점이 생겼다

 

구글링을 해보니 QueryDSL는 2012년도에 실무에 처음 정착했다고 했다(생각보다 오래됬네)

이를 이해하기 위해서는 앞서 이해해야 하는 것들이 있다.

 

1. 엔티티란? 

@Entity 어노테이션이 붙은 자바 클래스이다. 엔티티 클래스는 테이블 한개와 매칭된다.

 

예시)

@Entity
@Table(name = "USER")
public class User {

    @Id
    @GeneratedValue
    @Column(name = "USER_ID")
    private Long id;

    @Column(name = "USER_NAME")
    private String name;
}

 

 

2. JPA란?

sql을 자바에서 객체(엔티티) 다루듯이 사용가능하게 만들어주는 도구이다.

쿼리메서드 라는 것을 사용해서 쿼리대신 사용한다.

 

예를 들어

findByNameAndAgeGreaterThan(String name, int age)

 

라는 메서드를 생성하여 호출하면 내부적으로

select u
from User u
where u.name = :name
  and u.age > :age

 

이러한 쿼리가 자동으로 생성된다. 

 

즉, 메서드명에서 사용된

find, by, and, greater, than 등은 정해진 쿼리 메서드 키워드 문법이다.

복잡해질수록 가독성이 무너진다는 단점이 있다.

 

그래서 단순한 쿼리를 반복적인 작성이 많은 프로젝트는 JPA를 사용하는 것이다. 

 

 

만약 쿼리메서드 키워드 조합으로는 표현이 불가능한 쿼리의 경우

@Query("""
    select u.id, u.name
    from User u
    where u.name = '홍길동'
""")

(위 코드는 findByName("홍길동") 로 대체 가능한 예시이긴 함)


이런식으로 어노테이션을 붙이고 쿼리를 직접 칠수 있다.

이는 sql과 비슷한 문법을 가진 JPQL라는 언어인데 엔티티 객체를 대상으로 하는 쿼리이다.

User -> 엔티티 클래스명

u.id ->  엔티티 필드 접근

 

*그냥 SQL을 사용하고 싶다면 "네이티브 쿼리"도 사용가능하다

 

 

"그럼 복잡한 쿼리는 결국 직접 치는데, JPA를 사용해야 하는 이유는 뭘까?"

 

그것은 바로

테이블 컬럼명이 바뀌더라도 엔터티만 수정하면 모든 쿼리를 수정하지 않아도 된다는 장점 때문이다. (엔티티 필드를 사용하기 때문)
엔티티 필드에 매칭되어있는 컬럼명을 어노테이션에서만 수정하면 끝난다.

 

즉, 서버가 DB에 덜 종속되기 때문에 결합도가 감소하여 DB수정이 자유롭고 유지보수가 편해진다.

 


한마디로 정리하면 JPA는 개발이 끝난 뒤에 DB 테이블/컬럼이 바뀌어도 코드 전체를 안 뒤엎게 하려고 쓰는 거다.

 

 

 

3. QueryDSL이란?

JPA위에서 돌아가는 추가 라이브러리 이다.


사용하는 이유는 JPQL이 문자열이라 위험하기 때문이다. (오타가 나도 컴파일은 통과를 하는데 실행시 터진다.)


그래서

queryFactory
  .selectFrom(servicePipe)
  .where(
      servicePipe.useYn.isTrue(),
      servicePipe.pipeDia.eq(pipeDia),
      servicePipe.pipePress.eq(pipePress)
  )
  .fetch();


이런식으로 selectFrom(), where(), isTrue(), eq()등의 미리 정해진 메서드를 제공하는 확장판 라이브러리이다.

 

JPA의 쿼리메서드는 메서드명으로 밖에 동작을 지정할수 밖에 없는데 

QueryDSL는 이렇게 제공되는 메서드를 조합해서 쿼리를 작성할 수 있다.

 

 

<비교 예시>

 

 쿼리메서드)

findByNameAndAgeGreaterThan(String name, int age)

 

QueryDSL)

QUser user = QUser.user;

List<User> result = queryFactory
    .selectFrom(user)
    .where(
        user.name.eq(name),
        user.age.gt(age)
    )
    .fetch();

 

즉, 

Query Method는 “미리정해진 키워드 순서로 메서드명을 써라” 라고 한다면

QueryDSL는 미리정해진 메서드들을 조합해라” 라고 할 수 있다.

 

 

 

위 예시에서 사용된

 

QUser user = QUser.user;

 

이는  'Q클래스'라고 하는데 QueryDSL이 제공하는 클래스 기능이다.

빌드할때 엔티티를 기반으로 자동생성되는 클래스인데(빌드안하면 오류남)

테이블 컬럼을 자바 필드로 표현할 수 있게 해주는 기능으로 QueryDSL를 작동하는데 필수적이다.

 

Q클래스의 존재 이유는

QueryDSL에서 엔티티를 좀더 안전하게 사용하기 위함이다.

엔티티 필드가 틀렸거나 엔티티명이 틀렸을 경우 빌드시 오류가 나서 잡을 수 있다.

 

 

3. Impl 란?

 QueryDSL 쿼리를 모아둔 파일을 Impl라고 한다.

 

Implementation(구현)의 약자이며, 여기서는 레파지토리 인터페이스를 구현한 클래스를 뜻한다.

 

보통 JPA에서 impl은 커스텀 레파지토리를 구현할때 필요한데, 기본적으로 간단한 CURD는 전부 JPA로 처리를 하고 조회(SELECT) 조건이 복잡해지기 시작하면 QueryDSL를 사용한다.

 

실무적으로 보통 저장로직은(INSERT / UPDATE / DELETE) 간단한 경우가 많기 때문에 JPA만 사용한다.

 

 

public interface UserRepository extends JpaRepository<User, Long> {
}

만약 위 예시처럼 레파지토리 클래스가 JpaRepository를 상속한 경우에 

스프링이 기본 레파지토리로 이를 인식해서 내부적으로 구현클래스를 자동으로 생성한다.

 

즉, '기본 레파지토리'는 JPA를 사용할때 구현클래스(impl)을 개발자가 직접 만들 필요가 없다.

 

public interface UserRepositoryCustom {
    List<User> searchUsers(String name, Integer age);
}

다만 위와같은 경우는 스프링이 자동으로 구현할 수 없기 때문에 개발자가 직점 구현클래스(Impl)를 만들어야 한다.

 

추가로,

public interface UserRepository extends
        JpaRepository<User, Long>,
        UserRepositoryCustom {
}

이렇게 하면 기본 CRUD기능과 커스텀기능도 탑재한 레파지토리로 합쳐서 사용이 가능하다.