본문 바로가기
Java & Spring

메서드명 쿼리 vs @Query vs QueryDSL 비교

by yamoojin83 2025. 10. 2.

메서드명 쿼리 vs @Query vs QueryDSL 비교

Spring Data JPA는 쿼리를 작성하는 방법이 여러 가지입니다. 메서드명 유도, @Query(JPQL/Native), QueryDSL 중 언제 무엇을 선택할지 기준을 세우면, 저장소 코드가 짧고 안정적입니다.

1. 메서드명 쿼리(파생 쿼리)


interface OrderRepository extends JpaRepository<Order, Long> {
  List<Order> findTop20ByStatusAndCreatedAtBetweenOrderByIdDesc(
      Status status, LocalDateTime from, LocalDateTime to);
}
  • 장점: 빠르고 타입 안전, 간단한 조건/정렬에 적합
  • 한계: 조건이 늘면 메서드명이 폭발, 동적 조합이 어려움

2. @Query (JPQL/Native)


@Query("select o from Order o join fetch o.member m " +
       "where (:status is null or o.status = :status) " +
       "and o.createdAt between :from and :to")
List<Order> search(@Param("status") Status status,
                    @Param("from") LocalDateTime from,
                    @Param("to") LocalDateTime to);
  • 장점: 복잡한 조인/페치, DTO 프로젝션, countQuery 분리 가능
  • 한계: 문자열 쿼리라 리팩터링 시 깨지기 쉽고, 동적 조건 분기가 지저분해짐

3. QueryDSL (추천: 커스텀 리포지토리)


public interface OrderQueryRepository {
  List<OrderView> search(OrderCond cond, Pageable pageable);
}

@Repository
@RequiredArgsConstructor
class OrderQueryRepositoryImpl implements OrderQueryRepository {
  private final JPAQueryFactory query;

  @Override
  public List<OrderView> search(OrderCond cond, Pageable pageable) {
    QOrder o = QOrder.order; QMember m = QMember.member;
    BooleanBuilder where = new BooleanBuilder();
    if (cond.status() != null) where.and(o.status.eq(cond.status()));
    if (cond.from() != null) where.and(o.createdAt.goe(cond.from()));
    if (cond.to() != null) where.and(o.createdAt.loe(cond.to()));

    return query
      .select(Projections.constructor(OrderView.class, o.id, m.name, o.createdAt))
      .from(o).join(o.member, m)
      .where(where)
      .orderBy(o.id.desc())
      .offset(pageable.getOffset())
      .limit(pageable.getPageSize())
      .fetch();
  }
}
  • 장점: 타입 안전, 동적 조건/정렬/프로젝션이 깔끔, 대규모 화면 적합
  • 한계: 초기 설정(Q클래스 생성) 필요, 러닝커브

선택 기준

  • 간단한 조회(필드 1~2개, 정렬 고정) → 메서드명
  • 고정된 복잡 쿼리/DTO/페치조인 → @Query
  • 동적 검색/필터/다양한 정렬/대시보드 → QueryDSL

프로젝션 전략


// 인터페이스 프로젝션
interface OrderSummary {
  Long getId();
  String getMemberName();
}
// 클래스 프로젝션 (생성자)
public record OrderView(Long id, String memberName, LocalDateTime createdAt) {}

테스트 팁

  • 쿼리 결과 스키마(컬럼 개수/별칭)와 DTO 생성자/인터페이스 매핑을 반드시 검증
  • 동적 조건은 파라미터 조합별 테이블 주도 테스트(경계값 from/to 포함)

FAQ

Q. 네이티브는 언제?
A. DB 함수/힌트/윈도우 함수가 꼭 필요할 때만. 가능하면 JPQL/QueryDSL 유지.

Q. 페치조인과 페이징?
A. XToOne은 비교적 안전하지만 컬렉션 페치조인은 권장하지 않습니다.

 

 

👉 1편: 연관관계 주인/지연로딩 N+1 체크리스트

👉 2편: 메서드명 쿼리 vs @Query vs QueryDSL 비교

👉 3편: @Transactional 전파/고립수준 이해

👉 4편: 페이징 성능: 카운트 최적화·키셋 페이징