Doma入門 - Criteira APIで射影を使う

## はじめに

SELECT句で特定のカラムだけを指定して取得することを射影と呼びますが、全てのカラムを取得すると都合が悪い場合（カラムの数が多すぎる、LOBのような容量の大きなカラムが存在するなど）に便利な手法です。

今回は、[Doma](https://github.com/domaframework/doma) のCriteria APIで射影を表現する方法を説明します。

Criteria APIで射影を実現する方法は2つあります。

* エンティティクラスを使って取得する
* タプルクラスを使って取得する

ここで使うDomaのバージョンは3.6.0です。

DomaやCriteria APIの概要については[Doma入門](https://qiita.com/nakamura-to/items/deaff05ba530317f3cf8)の他の記事もお読みください。

## この記事で使うサンプルコード

簡略化したコードで説明します。より詳しいサンプルコードについては下記のプロジェクトを参照してください。

* https://github.com/domaframework/simple-examples/

データベースには従業員を表すemployeeテーブルがあります。

```sql:schema.sql
create table employee (
    id integer not null primary key,
    name varchar(255) not null,
    age integer not null,
    salary integer,
    job_type varchar(20),
    hiredate timestamp, 
    department_id integer, 
    version integer not null, 
    insertTimestamp timestamp, 
    updateTimestamp timestamp);
```

employeeテーブルに対応するEmployeeクラスを用意します。

```java:Employee.java
@Entity(metamodel = @Metamodel)
public class Employee {
  @Id
  Integer id;
  String name;
  Age age;
  Salary salary;
  @Column(name = "JOB_TYPE")
  JobType jobType;
  LocalDate hiredate;
  @Column(name = "DEPARTMENT_ID")
  Integer departmentId;
  @Version
  @Column(name = "VERSION")
  Integer version;
  LocalDateTime insertTimestamp;
  LocalDateTime updateTimestamp;

  // getter, setter
}
```

射影した結果を保持するDTO（ValueObject）を用意します。

```java:NameAndSalaryDto.java
public class NameAndSalaryDto {

  private final String name;
  private final Salary salary;

  public NameAndSalaryDto(String name, Salary salary) {
    this.name = name;
    this.salary = salary;
  }

  // getter
}
```

`EmployeeRepository`を用意し、このクラスにいくつかメソッドを追加して例を示していきます。

```java:EmployeeRepository.java
public class EmployeeRepository {

  private final QueryDsl queryDsl;

  public EmployeeRepository(Config config) {
    this.queryDsl = new QueryDsl(config);
  }
}
```

## エンティティクラスを使って取得する

これはすでにあるデータ構造を再利用できると言う意味で非常にお手軽な方法です。

`projectTo`もしくは`selectTo`メソッドを使えば射影した値をエンティティの特定のプロパティにセットできます（その他のプロパティにはnullや初期値がセットされます）。
`projectTo`メソッドは結果から重複したエンティティIDを削除しますが、`selectTo`メソッドは削除しません。

```java
  public List<Employee> selectNameAndSalaryAsEntityList() {
    Employee_ e = new Employee_();
    return queryDsl.from(e).selectTo(e, e.name, e.salary).fetch();
  }
```

発行されるSQLは次のものになります。

```sql
select t0_.id, t0_.name, t0_.salary from Employee t0_
```

nameとsalaryの2つのカラムがSELECT句に指定されたSQLになります。指定していない主キーが含まれていますが、これは仕様で、結果セット内においてエンティティの一意性を保証するために使われます。

## タプルクラスを使って取得する

Domaは、`org.seasar.doma.jdbc.criteria.tuple`パッケージで`Tuple2`、`Tuple3`、...、`Tuple9`といった値の組み合わせを表すクラスを提供します。
クラス名の末尾の数字はそのクラスでいくつの値を扱えるかと言うことを表しています。

次は`Tuple2`クラスを使って2つのカラムを射影する例です。

```java
  public List<Tuple2<String, Salary>> selectNameAndSalary() {
    Employee_ e = new Employee_();
    return queryDsl.from(e).select(e.name, e.salary).fetch();
  }
```

発行されるSQLは次のものになります。

```sql
select t0_.name, t0_.salary from Employee t0_
```

nameとsalaryの2つのカラムのみがSELECT句に指定されたSQLになっています。

Tuple2〜9のクラスをどのレイヤーでどのように利用するかは自由ですが、以下のようにアプリケーションのDTO（もしくはValueObject）に変換するまでの一時的なデータ構造として使うのが綺麗な設計だと思います。

```java
  public List<NameAndSalaryDto> selectNameAndSalaryAsNameAndSalaryDtoList() {
    Employee_ e = new Employee_();
    return queryDsl.from(e).select(e.name, e.salary).stream()
        .map(tuple -> new NameAndSalaryDto(tuple.getItem1(), tuple.getItem2()))
        .collect(Collectors.toList());
  }
```

10個以上のカラムを指定して受け取る場合は`org.seasar.doma.jdbc.criteria.tuple.Row`インタフェースで表現することもできますが、カラムの数が多い場合は読み取り専用のエンティティクラスを作成したほうがわかりやすいでしょう。

## おわりに

Criteria APIで射影を表現する方法を説明しました。

TupleクラスのようなものがJavaの標準にあればそれを使いたいところですが、実際にはないのが悩ましいところですね。

なお、DomaのTuple2〜9クラスはKotlinで使われることも意識しているのでdestructuring declarationに対応しています。そのためKotlinでは次のように扱えます。

```kotlin
val (number, string) = Tuple2(1, "a")
```