QueryDSL을 사용하다 보면 자주 마주치는
Q클래스,Path,객체 그래프 탐색이라는 개념들. 처음엔 다소 낯설 수 있지만, 이를 제대로 이해하면 QueryDSL의 핵심 원리를 꿰뚫어볼 수 있다.
객체 그래프 (Object Graph) 는 말 그대로 "객체들 간의 참조 관계를 그래프로 표현한 것"이다.
예를 들어 아래와 같은 엔티티 구조가 있다고 할 때:
@Entity
public class Todo {
    @Id
    private Long id;
    private String title;
    private String comment;
    @ManyToOne
    private User user;
}
@Entity
public class User {
    @Id
    private Long id;
    private String name;
}
이 구조는 다음과 같은 객체 그래프로 표현된다:
Todo
 ├── title
 ├── comment
 └── User
       └── name
이 객체 그래프를 따라 우리는 todo.user.name 과 같이 연관된 객체의 필드까지 탐색할 수 있다.
QueryDSL은 빌드 시점에 각 엔티티에 대해 Q클래스를 생성한다.
예: Todo → QTodo, User → QUser
public class QTodo extends EntityPathBase<Todo> {
    public static final QTodo todo = new QTodo("todo");
    
    public final StringPath title = createString("title");
    public final StringPath comment = createString("comment");
    
  
    public final QUser user;
    
  
    public QTodo(String variable) {
        super(Todo.class, forVariable(variable));
    }
}
title, comment 필드는 각각 StringPath 타입의 인스턴스 변수로 선언됨StringPath는 QueryDSL의 Path<String> 인터페이스를 구현한 클래스이며,PathImpl<String>를 필드로 포함하여 공통 로직을 제공한다.Path 인터페이스를 구현한 다양한 타입이 존재한다:| 필드 타입 | Q 클래스의 Path 타입 | 
|---|---|
String | StringPath | 
Long | NumberPath<Long> | 
Date | DateTimePath<...> | 
User | QUser (연관 객체, 또 다른 Q 클래스) | 
이러한 Path 객체들은 QueryDSL 내부에서 경로(Path) 로 사용된다.
예: todo.user.name → QTodo → QUser → StringPath
따라서 Path는 객체 그래프의 각 간선(edge) 에 해당한다고 볼 수 있고,
Q 클래스는 각 필드를 Path 객체로 래핑한 정적인 경로 모음이라 할 수 있다.
QueryDSL은 Q 클래스에 정의된 Path들을 활용해 SQL 쿼리를 타입 안전하게 조립한다.
예를 들어 다음과 같은 코드:
queryFactory.selectFrom(todo)
    .join(todo.user, user)
    .where(user.name.eq("kim"))
    .fetch();
이 코드는 실제로는 다음과 같은 경로를 따라 객체 그래프를 탐색한 결과다:
QTodo.todo.user.name → Todo.user.name
todo.user.userType.name처럼 경로가 객체의 참조처럼 이어지기 때문에.) 연산을 통해 자동완성이 가능하다."t.user.userType.name"처럼 문자열 기반이라 자동완성이 불가능하다.fetchJoin()을 통해 연관된 객체를 명시적으로 fetch할 수 있다.queryFactory
    .selectFrom(todo)
    .join(todo.user, user).fetchJoin()
    .join(user.userType, userType).fetchJoin();
| 개념 | 설명 | 
|---|---|
| 객체 그래프 | 객체 간의 참조 관계를 그래프 형태로 표현한 구조 (예: Todo → User → name) | 
| Q 클래스 | 엔티티를 기반으로 QueryDSL이 생성한 쿼리용 클래스. 각 필드를 Path 객체로 래핑 | 
| Path | 쿼리에서 사용할 수 있도록 필드를 감싼 객체. StringPath, NumberPath 등으로 타입화 | 
| QueryDSL | Q 클래스에 정의된 Path들을 조합해 객체 그래프를 따라가며 쿼리를 타입 안전하게 구성하는 DSL | 
Q 클래스는 컴파일 타임에 생성되며, 각 필드를 Path 객체로 래핑한 정적 구조이다.
QueryDSL은 이 Path 객체들을 통해 객체 그래프를 타입 안전하게 탐색하고,
SQL 쿼리를 객체 참조처럼 구성할 수 있도록 돕는 DSL(Domain-Specific Language)이다.
이 구조를 이해하면 QueryDSL을 단순한 SQL 빌더가 아닌,
객체 지향적 쿼리 언어로 활용하는 핵심 원리를 파악할 수 있다.
[Java 코드]
queryFactory
.select(todo.user.name)
.from(todo)
.join(todo.user, user)
.where(user.name.eq("kim"))
.fetch();
     │
     ▼
[Q 클래스 기반 Path 조립]
todo → QTodo
└── user → QUser
└── name → StringPath
→ 각 필드는 컴파일 타임에 생성된 Path 객체이며, 객체 그래프의 경로를 타입 안전하게 표현함
     │
     ▼
[QueryDSL이 SQL 문자열로 변환]
SELECT u.name
FROM todo t
JOIN user u ON t.user_id = u.id
WHERE u.name = 'kim'
→ QueryDSL이 Path 정보를 기반으로 실제 SQL을 조립함
     │
     ▼
[JPA가 SQL 실행 → 결과 매핑]
QueryDSL이 생성한 JPQL 기반 쿼리를 EntityManager가 실행 요청하고,
Hibernate가 이를 실제 SQL로 변환하여 DB에 전달함.  
DB에서 반환된 ResultSet은 Hibernate에 의해 Java 객체로 매핑됨
이 때 select(todo.user.name)은 user 테이블의 name 컬럼만 조회하며,
해당 값은 문자열 타입으로 매핑되어 Java의 String으로 반환됨
     │
     ▼
[Java 결과로 반환]
→ QueryDSL은 select 대상에 따라 반환 타입(T)을 결정하고,
fetch 메서드에 따라 단건 또는 다건 결과를 반환함
예)
| 구성 요소 | 설명 | 
|---|---|
| type | 필드의 자바 타입. 예: String.class, Long.class | 
| metadata | PathMetadata 객체. 필드명·전체 경로·별칭(alias) 등을 보유 | 
| parent | 상위 Path 객체 (e.g. todo.user.name에서 name.parent는 user) | 
| root | 루트 Path (예: QTodo.todo) — 전체 객체 그래프의 출발점 | 
| annotatedElement (선택)  | 원본 자바 필드나 메서드를 나타내는 리플렉션 객체. 코드 생성 도구에서 사용하고 일반 사용자 코드에서는 거의 사용되지 않음 | 
예시
StringPath title = new StringPath(QTodo.todo, "title");
→type = String.class,parent = QTodo.todo,root = QTodo.todo,metadata = "todo.title"
좋은 글 정독했습니다 잘 봤습니다 이대로 글 쓰면 좋은 것 같ㅡ니