JPA

JPQL이 있는데 왜 Querydsl을 쓸까?

경딩 2026. 2. 25. 23:36

같은 쿼리로 비교하며 이해하는 Querydsl 의 진짜 가치

Querydsl 을 접하면 이런 의문이 든다.

JPQL도 있는데 왜 또 다른 쿼리 도구를 써야 하지?

 

겉으로 보면 JPQL과 Querydsl 은 결국 같은 JPAL을 실행한다. 하지만 실무에서의 차이는 문법이 아니라 오류를 발견하는 시점과 안전성에 갈린다.

 

이 글에서는 동일한 쿼리를 JPQL과 Querydsl로 각각 작성해보면서 두 방식의 차이를 코드와 동작 관점에서 직접 비교해본다.

 

이 글을 읽고 나면 네 가지를 명확히 이해하게 된다.

  • JPQL과 Querydsl의 차이를 단순 문법이 아닌 안전성 관점에서 비교할 수  있다.
  • Q 타입이 왜 존재하는지, 컴파일 타임 검증 구조를 이해하게 된다.
  • Querydsl 의 자동 파라미터 바인딩이 보안과 성능에 주는 이점을 알 수 있다.
  • JPAQueryFactory 를 필드로 선언해도 안전한 이유를 스프링 동작 원리 관점에서 납득하게 된다.

 

테스트 환경 세팅

먼저 JPQL과 Querydsl을 동일한 조건에서 비교하기 위해 테스트 환경을 구성한다.

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Autowired
    private EntityManager em;

    JPAQueryFactory queryFactory;

    @BeforeEach
    public void setUp() {
        queryFactory = new JPAQueryFactory(em);

        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
    }
}

 

JPAQueryFactory 생성 시 EntityManager 를 넘겨받는다. 내부적으로 이 EntityManager 를 사용해 실제 JPQL을 생성하고 실행하는 구조다.


JPQL로  member1 조회하기

 

먼저 전통적인 JPAL방식이다.

@Test
public void startJPQL() {
    String jpql = "select m from Member m where m.name = :name";

    Member findMember = em.createQuery(jpql, Member.class)
            .setParameter("name", "member1")
            .getSingleResult();

    Assertions.assertThat(findMember.getName()).isEqualTo("member1");
}

JPQL의 특징은 쿼리를 문자열로 작성한다는 점이다. :name 과 같은 파라미터를 선언하고 setParameter() 로 값을 직접 바인딩한다. 문법 자체는 단순하고 직관적이지만 구조적인 한계가 있다.

 

가장 큰 문제는 필드명 오타가 있어도 컴파일 단계에서 잡히지 않는다는 것이다. 쿼리 오류는 실제 실행되는 런타임에야 발견된다. 코드가 정상 컴파일 되더라도 배포 이후에 쿼리 오류가 터질 수 있는 구조다.


Querydsl로 member1 조회하기

이제 동일한 쿼리를 Querydsl로 작성해보자.

@Test
public void startQuerydsl() {
    QMember member = QMember.member; // 이미 생성된 Q타입 사용

    Member findMember = queryFactory
            .select(member)
            .from(member)
            .where(member.name.eq("member1"))
            .fetchOne();

    Assertions.assertThat(findMember.getName()).isEqualTo("member1");
}

Querydsl은 JPAQueryFactory를 시작점으로, Q 타입을 이용해 쿼리를 자바 코드 형태로 작성한다. 여기서 등장하는  QMember 는 Querydsl 이 생성하는 메타모델 클래스다. Gradle에서 compileQuerydsl을 실행하면 자동으로 생성된다.

 

QMember member = QMember.member;

 

일반적으로 이미 생성된 정적 인스턴스를 사용한다. new QMember("m")처럼 직접 생성할 수도 있지만, 생성자에 전달된 문자열은 별칭(alias)용도이기 때문에 특수한 조인 상황이 아니라면 기본 인스턴스를 사용하는 것이 일반적이다.


JPQL과 Querydsl의 핵심 차이

1. 오류 발견 시점 — 런타임 vs 컴파일 타임

JPQL은 문자열 기반 쿼리다.

"select m from Member m where m.nmae = :name" // 오타 발생

 

위와 같이 필드명을 잘못 작성해도 컴파일러가 이를 검증할 수 없다. 애플리케이션이 실행되고 해당 쿼리가 호출되는 시점에서야 오류가 발생한다.

 

반면 Querydsl 은 Q타입을 기반으로 쿼리를 작성한다.

member.name.eq("member1")

 

존재하지 않는 필드를 호출하면 컴파일 단계에서 즉시 오류가 발생한다.  Querydsl 의 진짜 가치는 문법 편의성이 아니라 컴파일 타임 안전성에 있다.


2. 파라미터 바인딩 방식

JPQL은 파라미터를 직접 선언하고 바인딩해야 한다.

.setParameter("name", "member1")

 

반면 Querydsl은 값을 직접 전달해도 된다.

.where(member.name.eq("member1"))

 

내부적으로 문자열을 직접 이어붙이는 방식이 아니라 JDBC의 PreparedStatement 기반 파라미터 바인딩이 자동으로 적용된다. 실행 로그를 보면 값이 ? 로 바인딩되는 것을 확인할 수 있다.

 

이 구조는 두 가지 장점을 가진다. SQL Injection  공격에 대한 방어, 그리고 동일한 쿼리 구조 재사용으로 인한 DB 성능 이점이다. Querydsl은 편의성뿐 아니라 보안과 성능 측면에서도 안전한 기본 구조를 제공한다.


JPAQueryFactory를 필드로 선언해도 안전한 이유

실무에서 자주 나오는 질문 중 하나가 이것이다.

"JPAQueryFactory를 필드로 선언하면 멀티스레드 환경에서 안전할까?"

결론부터 말하면 안전하다.

 

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Autowired
    private EntityManager em;

    JPAQueryFactory queryFactory; // 필드 레벨 선언
}

 

그 이유는 스프링이 주입하는 EntityManager 가 실제 구현체가 아니라 트랜잭션 스코프 프록시이기 때문이다. 요청마다 새로운 EntityManager 를 직접 공유하는 것이 아니라, 현재 트랜잭션에 바인딩된 EntityManager 가 동적으로 할당된다. 따라서 여러 스레드가 동시에 접근하더라고 각 트랜잭션에 맞는 EntityManager 가 사용되며 동시성 문제 없이 동작한다.

 

이 구조 덕분에 JPAQueryFactory 는 필드로 선언하여 재사용하는 것이 일반적으로 권장방식이다.

 


정리: Querydsl의 진짜 가치는 가독성이 아니다

많은 사람들이 Querydsl을 "코드가 더 깔끔해지는 쿼리 도구"라고 생각한다. 하지만 실무에서의 핵심 가치는 가독성이 아니다.

 

  JPQL Querydsl
쿼리 작성 방식 문자열 자바 코드
오류 발견 시점 런타임 컴파일 타임
파라미터 바인딩 직접 처리 자동 처리
SQL Injection 방어 주의 필요 자동 방어
리팩토링 안정성 낮음 높음

 

결국 Querydsl은 단순히 쿼리를 편하게 작성하는 도구라기보다, 런타임에 발생할 수 있는 쿼리 리스크를 컴파일 타임으로 끌어올리는 설계 도구에 가깝다. 대규모 프로젝트나 유지보수가 중요한 환경일수록 이 차이는 점점 더 크게 체감된다.