Skip to the content.

Query DTO Guide — 조회 조건 데이터

Query DTO는 Read 작업의 검색 조건을 담는 순수한 불변 객체입니다.

Java Record 패턴을 사용하며, 데이터 전달만 담당합니다.


1) 핵심 역할


2) 핵심 원칙

원칙 1: 순수 Java Record

원칙 2: 데이터 전달 전용

원칙 3: 페이징 지원

원칙 4: 네이밍 규칙


3) 패키지 구조

application/{bc}/dto/query/
├── Get{Bc}Query.java
├── Search{Bc}Query.java
└── Search{Bc}CursorQuery.java

4) 템플릿 코드

단건 조회

package com.ryuqq.application.{bc}.dto.query;

public record Get{Bc}Query(
    Long id
) {}

Offset 페이징

package com.ryuqq.application.{bc}.dto.query;

import java.time.Instant;

public record Search{Bc}Query(
    Long filterId,
    String status,
    Instant startDate,
    Instant endDate,
    String sortBy,        // 정렬 필드 (예: "createdAt", "name")
    String sortDirection, // 정렬 방향 (예: "ASC", "DESC")
    Integer page,
    Integer size
) {}

No-Offset (커서) 페이징

package com.ryuqq.application.{bc}.dto.query;

import java.time.Instant;

public record Search{Bc}CursorQuery(
    Long lastId,
    String status,
    Instant startDate,
    Instant endDate,
    Integer size
) {}

5) 실전 예시

Offset 페이징

package com.ryuqq.application.order.dto.query;

import java.time.Instant;

public record SearchOrdersQuery(
    Long customerId,
    String status,
    Instant startDate,
    Instant endDate,
    String sortBy,        // 정렬 필드: "orderedAt", "totalAmount" 등
    String sortDirection, // 정렬 방향: "ASC", "DESC"
    Integer page,
    Integer size
) {}

No-Offset (커서) 페이징

package com.ryuqq.application.order.dto.query;

import java.time.Instant;

public record SearchOrdersCursorQuery(
    Long lastOrderId,
    String status,
    Instant startDate,
    Instant endDate,
    Integer size
) {}

6) Do / Don’t

❌ Bad

// ❌ jakarta.validation
import jakarta.validation.constraints.*;
public record Query(@Min(0) Integer page) {}

// ❌ 기본값 처리
public record Query(Integer page, Integer size) {
    public Query {
        page = (page == null) ? 0 : page;  // REST API 책임!
    }
}

// ❌ 비즈니스 검증
public record Query(Instant startDate, Instant endDate) {
    public Query {
        if (startDate.isAfter(endDate)) {  // 비즈니스 로직!
            throw new IllegalArgumentException();
        }
    }
}

✅ Good

// ✅ 순수 Record
public record Query(
    Instant startDate,
    Instant endDate,
    Integer page,
    Integer size
) {}

7) Offset vs No-Offset

Offset

public record Query(Integer page, Integer size) {}
// SQL: OFFSET page * size LIMIT size
// 사용: 관리자 페이지, 페이지 번호 필요

No-Offset (커서)

public record Query(Long lastId, Integer size) {}
// SQL: WHERE id > lastId LIMIT size
// 사용: 무한 스크롤, 대용량 데이터

8) 체크리스트


작성자: Development Team 최종 수정일: 2025-11-13 버전: 3.0.0 (단순화)