# 프로젝션과 결과 반환

* 프로젝션
  * select 할 대상을 지정하는 것

## 프로젝션 대상이 하나일 때

```java
class Projection {
    void example() {
        List<String> result = queryFactory
                .select(member.username)
                .from(member)
                .fetch();
    }
}
```

* 대상이 하나면 타입을 명확하게 지정할 수 있다.

## 프로젝션 대상이 둘일 때

### Tuple

```java
class Projection {
    void example() {
        List<Tuple> result = queryFactory
                .select(member.username, member.age)
                .from(member)
                .fetch();

        for (Tuple tuple : result) {
            // 프로젝션 한 데이터를 각각 꺼내서 사용하면 된다.
            String username = tuple.get(member.username);
            Integer age = tuple.get(member.age);

            System.out.println("username=" + username);
            System.out.println("age=" + age);
        }
    }
}
```

* 튜플
  * 결과가 여러 개일 때 담을 수 있도록 만든 Querydsl 객체
  * 리포지토리 계층에서만 사용하고 다른 레이어가 종속되지 않게 한다.
* 대상이 둘 이상이면 튜플이나 DTO로 조회해야 한다.

### 순수 JPA에서 DTO 조회

{% tabs %}
{% tab title="MemberDto.java" %}

```java
@Data
public class MemberDto {
    private String username;
    private int age;

    public MemberDto() {
    }

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}
```

{% endtab %}
{% endtabs %}

* 엔티티로 가져오면 필요하지 않은 데이터도 다 가져와야 하므로 필요한 데이터만 가져올 수 있게 DTO를 만든다.
* Querydsl이 일단 MemberDto를 만든 다음 작업하기 때문에 MemberDto에 디폴트 생성자가 꼭 필요하다.

{% tabs %}
{% tab title="QuerydslBasicTest.java" %}

```java
@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Test
    void findDtoByJPQL() {
        List<MemberDto> result = em.createQuery(
                        "select new study.querydsl.dto.MemberDto(m.username, m.age) " +
                                "from Member m", MemberDto.class)
                .getResultList();
    }
}

```

{% endtab %}
{% endtabs %}

* select에 DTO 타입을 new 명령어로 명시해준다.
  * `select m from Member m` 이라고 하면 Member 엔티티를 조회하기 때문에 타입이 맞지 않는다.
* DTO의 패키지 일므을 다 적어야 해서 지저분하다.
* 생성자 방식만 지원한다.

### Querydsl 빈 생성

{% tabs %}
{% tab title="프로퍼티 접근" %}

```java
@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Test
    void findDtoBySetter() {
        List<MemberDto> result = queryFactory
                // bean이 setter로 주입해준다.
                .select(Projections.bean(
                        MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
    }
}

```

{% endtab %}

{% tab title="필드 직접 접근" %}

```java
@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Test
    void findDtoByField() {
        List<MemberDto> result = queryFactory
                // 필드에 바로 넣는다.
                .select(Projections.fields(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
    }
}

```

{% endtab %}

{% tab title="생성자 사용" %}

```java
@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Test
    void findDtoByConstructor() {
        List<MemberDto> result = queryFactory
                // 생성자를 사용한다.
                .select(Projections.constructor(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
    }
}
```

{% endtab %}
{% endtabs %}

### 별칭이 다를 때

{% tabs %}
{% tab title="UserDto.java" %}

```java
@Data
public class UserDto {
    private String name;
    private int age;
}
```

{% endtab %}

{% tab title="QuerydslBasicTest.java" %}

```java
@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Test
    void findDtoByConstructor() {
        QMember memberSub = new QMember("memberSub");

        List<MemberDto> result = queryFactory
                .select(Projections.constructor(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
    }
}
```

{% endtab %}
{% endtabs %}

* username인데 DTO에는 name으로 되어있어 일치하지 않을 경우 사용한다.

```
ExpressionUtils.as(source, alias)
```

* 필드나 서브 쿼리에 별칭을 적용한다.

```
username.as("memberName")
```

* 필드에 별칭을 적용한다.

## @QueryProjection

{% tabs %}
{% tab title="MemberDto.java" %}

```java
@Data
public class MemberDto {
    private String username;
    private int age;

    public MemberDto() {
    }

    @QueryProjection
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}
```

{% endtab %}

{% tab title="QMemberDto.java" %}

```java
/**
 * study.querydsl.dto.QMemberDto is a Querydsl Projection type for MemberDto
 */
@Generated("com.querydsl.codegen.DefaultProjectionSerializer")
public class QMemberDto extends ConstructorExpression<MemberDto> {

    private static final long serialVersionUID = 1356709634L;

    public QMemberDto(com.querydsl.core.types.Expression<String> username, com.querydsl.core.types.Expression<Integer> age) {
        super(MemberDto.class, new Class<?>[]{String.class, int.class}, username, age);
    }

}

```

{% endtab %}
{% endtabs %}

* 생성자에 @QueryProjection을 붙인 뒤 compileQuerydsl 한다.
  * QMemberDto 클래스가 생성된다.

{% tabs %}
{% tab title="QuerydslBasicTest.java" %}

```java
@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Test
    void findDtoByQueryProjection() {
        List<MemberDto> result = queryFactory
                // DTO 클래스를 new로 바로 넣으면 된다.
                // 생성자로 만들기 때문에 타입과 파라미터를 다 맞춰줘서 안정적으로 만들 수 있다.
                .select(new QMemberDto(member.username, member.age))
                .from(member)
                .fetch();
    }
}

```

{% endtab %}
{% endtabs %}

* 컴파일러로 타입을 체크할 수 있어 안전하다.
  * 생성자를 통한 빈 생성 방식은 필드를 다르게 넣어도 컴파일 시점에 잡을 수 없다.
* DTO에 Querydsl 애너테이션을 유지하고 Q 파일까지 만들어야 하는 단점이 있다.
  * Querydsl에 의존적인 설계가 된다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dodeon.gitbook.io/study/kimyounghan-querydsl/04-advanced/projection-and-return-result.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
