Repositoryを使った検索で特定カラムだけをPOJOへマッピングする

# 概要

Spring Data JPAのRepositoryを使った検索で必要なカラムだけを取得する方法があったのでコードを書いて動かしてみました。
いままではJPQLで`SELECT new com.example.domain.Fuga(...) FROM Hoge AS hoge ...`のような書き方をしていたのですが、だいぶ簡単に実装できるようになりました。

**環境**

* Windows10 Professional
* Java 1.8.0_144
* Spring Boot 1.5.6
    * Spring Data JPA 1.11.6
    * Hibernate 5.0.1
    * Lombok
* MySQL 5.6.25

**参考**

* [Spring Data JPA - Reference Documentation / 5.3.11. Projections] (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections)

# サンプルコード

## Entityクラス

```java
package com.example.domain.entity;

import com.example.domain.StandardType;
import lombok.*;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;

@Entity
@Table(name="item")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = {"itemStocks"})
@EqualsAndHashCode(exclude = {"itemStocks"})
public class Item implements Serializable {

    private static final long serialVersionUID = -3153084093423004609L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="name", nullable = false)
    private String name;
    @Column(name="price", nullable = false)
    private Integer price;
    @Column(name="sales_from", nullable = false)
    private LocalDateTime salesFrom;
    @Column(name="sales_to", nullable = false)
    private LocalDateTime salesTo;
    @Enumerated(EnumType.ORDINAL)
    @Column(name="standard_type", nullable = false)
    private StandardType standardType;
    @JoinColumn(name = "category_id", nullable = false)
    @ManyToOne
    private Category category;
    @Column(name="del_flag", nullable = false)
    private Boolean delFlag;
    @Column(name="create_at", nullable = false)
    private LocalDateTime createAt;
    @Column(name="update_at", nullable = false)
    private LocalDateTime updateAt;

    @OneToMany(mappedBy = "item", cascade = CascadeType.ALL)
    private List<ItemStock> itemStocks;
}
```

## Dtoクラス

Itemエンティティのうちid、name、priceだけを持つDtoクラスです。
Repositoryで検索した結果をこのクラスにマッピングします。

```java
package com.example.domain.dto;

import lombok.Value;

@Value
public class ItemNameAndPrice {
    private Long id;
    private String name;
    private Integer price;
}
```

## Repositoryクラス

```java
package com.example.domain.repository;

import com.example.domain.entity.Item;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ItemRepository extends JpaRepository<Item, Long> {
    // Class-based projections用の検索メソッド
    <T> T findOneById(Long id, Class<T> type);
}
```

### findOneを使った検索

```java
Item item = repository.findOne(1L);
```

発行されるSQL

```sql
select
    item0_.id as id1_1_0_,
    item0_.category_id as categor10_1_0_,
    item0_.create_at as create_a2_1_0_,
    item0_.del_flag as del_flag3_1_0_,
    item0_.name as name4_1_0_,
    item0_.price as price5_1_0_,
    item0_.sales_from as sales_fr6_1_0_,
    item0_.sales_to as sales_to7_1_0_,
    item0_.standard_type as standard8_1_0_,
    item0_.update_at as update_a9_1_0_,
    category1_.id as id1_0_1_,
    category1_.create_at as create_a2_0_1_,
    category1_.del_flag as del_flag3_0_1_,
    category1_.name as name4_0_1_,
    category1_.update_at as update_a5_0_1_ 
from
    item item0_ 
inner join
    category category1_ 
       on item0_.category_id=category1_.id 
where
    item0_.id=?
```

### Class-based projections

```java
ItemNameAndPrice nameAndPrice = repository.findOneById(1L, ItemNameAndPrice.class);
```

内部的にDtoをnewしているのは変わりません。

```java
select new com.example.domain.dto.ItemNameAndPrice(
    generatedAlias0.id,
    generatedAlias0.name,
    generatedAlias0.price) 
from
    Item as generatedAlias0 
where
    generatedAlias0.id=:param0
```

発行されるSQL

```sql
select
    item0_.id as col_0_0_,
    item0_.name as col_1_0_,
    item0_.price as col_2_0_ 
from
    item item0_ 
where
    item0_.id=?
```