데이터베이스 학습 노트 목차

QueryDSL — 타입세이프 동적 쿼리

JPQL이든 SQL이든 문자열로 쿼리를 짜면 두 가지가 괴롭다 — (1) 컴파일 타임에 오타·타입 오류를 못 잡고("selet"도 런타임에야 터짐), (2) 조건이 동적으로 바뀌는 검색(이름 있으면 이름으로, 가격대 있으면 가격으로…)을 StringBuilder로 잇다 보면 띄어쓰기·AND 지옥이 된다. QueryDSL은 *엔티티에서 생성한 메타모델(Q 클래스)*로 쿼리를 자바 코드처럼 타입 안전하게 조립해 이 둘을 푼다. (출처: OpenFeign/querydsl / Spring Data JPA — QueryDSL Extension.)

⚠️ 2026년 현재 — 어떤 QueryDSL을 쓰나

가장 먼저 알아야 할 실무 사실원조 com.querydsl(querydsl/querydsl)은 사실상 유지보수가 멈췄다(마지막 릴리스가 수년 전, Spring Boot 3·Jakarta 대응이 끊김). 그래서 커뮤니티가 이어받은 **OpenFeign 포크 io.github.openfeign.querydsl**가 Spring Boot 3.x/4.x·Jakarta(jakarta.persistence) 호환으로 활발히 릴리스되는 사실상의 표준이다. 신규 프로젝트는 groupId를 io.github.openfeign.querydsl 쓴다.

// build.gradle — OpenFeign 포크 (Jakarta / Spring Boot 3+)
implementation 'io.github.openfeign.querydsl:querydsl-jpa:<버전>:jakarta'
annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:<버전>:jakarta'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'

com.querydsl + javax.persistence 조합은 Spring Boot 3에서 깨진다(javax→jakarta 이행). 마이그레이션의 핵심이 이 groupId·classifier 교체다.

Q 클래스 — 엔티티의 타입세이프 거울

QueryDSL은 애너테이션 프로세서가 빌드 시 @Entity MemberQMember 같은 메타모델 클래스를 생성한다. 이 Q 클래스의 필드(member.email, member.age)로 조건을 타입 안전하게 쓴다 — 오타·타입 불일치는 컴파일 에러가 된다.

다이어그램 로딩 중…
JPAQueryFactory query = new JPAQueryFactory(entityManager);
QMember m = QMember.member;

List<Member> result = query
    .selectFrom(m)
    .where(m.age.goe(20)              // age >= 20  (타입 안전)
           .and(m.email.endsWith("@corp.com")))
    .orderBy(m.createdAt.desc())
    .limit(20)
    .fetch();

동적 쿼리 — QueryDSL이 가장 빛나는 곳

문자열 쿼리의 최대 약점조건 조립을 QueryDSL은 우아하게 푼다. BooleanBuilder로 조건을 모으거나, null을 반환하는 조건 메서드where()에 나열하면 null인 조건은 자동으로 무시된다.

// null 반환 패턴 — 입력이 없으면 그 조건은 그냥 빠진다
private BooleanExpression emailEq(String email) {
    return email != null ? member.email.eq(email) : null;
}
private BooleanExpression ageGoe(Integer age) {
    return age != null ? member.age.goe(age) : null;
}

List<Member> search(String email, Integer age) {
    return query.selectFrom(member)
        .where(emailEq(email), ageGoe(age))   // null 조건은 AND에서 제외
        .fetch();
}

StringBuilderWHERE 1=1 AND ...를 잇던 코드가 조건 메서드 나열로 깔끔해진다 — 동적 검색 화면의 정석 패턴이다.

조인·프로젝션·서브쿼리

// 페치 조인 + DTO 프로젝션 + 서브쿼리
List<MemberDto> dtos = query
    .select(Projections.constructor(MemberDto.class,
            member.email, member.age))           // 필요한 칼럼만 DTO로
    .from(member)
    .join(member.team, team).fetchJoin()         // N+1 방지 페치 조인
    .where(member.age.gt(
            JPAExpressions.select(member.age.avg()).from(member)))  // 서브쿼리
    .fetch();
  • 프로젝션Projections.constructor/fields/bean으로 필요한 칼럼만 DTO로(=SELECT * 회피, [07] 정신과 동일).
  • 페치 조인 — 연관을 한 방에 가져와 N+1을 막는다.
  • 집계·그룹화groupBy()·having()·Expressions로 윈도우/함수 일부도 표현.

QueryDSL vs JPQL vs 네이티브 SQL — 언제 무엇

타입 안전동적 쿼리DB 특화 기능
QueryDSL✅ 컴파일 검증✅ 최고△ 엔티티/표준 범위
문자열 JPQL❌ 런타임△ 번거로움△ JPA 추상
네이티브 SQL❌ 런타임✅ JSONB·CTE·윈도우 등 전부

원칙 — 동적 조건·복잡한 검색·타입 안정성이 중요하면 QueryDSL, DB 고유 기능(PostgreSQL jsonb @>·재귀 CTE·BRIN 힌트 등)이 절실하면 네이티브 SQL그 쿼리만 쓴다. 한 프로젝트에서 섞어 쓰는 게 보통이다.

한계와 비용

  • Q 클래스 생성 빌드 설정(annotationProcessor)이 필요하고, 엔티티가 바뀌면 재생성해야 한다.
  • DB 특화 문법은 표현에 한계가 있어, 그런 쿼리는 네이티브로 빠진다.
  • 결국 JPA/하이버네이트 위에서 도는 도구라 — 영속성 컨텍스트·페치 전략·N+1 이해가 선행돼야 효과를 본다.

한 줄 요약 — QueryDSL은 Q 클래스 메타모델타입세이프·동적 쿼리를 짠다. 2026년엔 OpenFeign 포크(io.github.openfeign.querydsl)·Jakarta가 사실상 표준(원조는 중단). 동적 검색엔 QueryDSL(null 조건 무시 패턴), DB 특화엔 네이티브 SQL을 섞어 쓴다.

(출처: OpenFeign/querydsl(GitHub) / Spring Data JPA — QueryDSL Extension / Spring Boot #48573 — io.github.openfeign.querydsl 이행.)

MySQL·InnoDB 심화 — 버퍼풀·redo/undo·8.4 LTS·MariaDB보안·권한 — 인증·인가·암호화·SQL 인젝션