Doma入門 - Criteria APIの紹介

## はじめに

[Doma](https://github.com/domaframework/doma) は Java 17 以上で動作するデータベースアクセスフレームワークです。JDBCドライバが提供されるデータベース、主なものでいえば、MySQL、PosgreSQL、Microsoft SQL Sever、H2 Database のようなデータベースにアクセスできます。

Doma には、型安全に SQL を組み立てるための API、[Criteira API](https://doma.readthedocs.io/ja/3.6.0/query-dsl/) があります。この記事では、Doma のバージョン3.6.0 に基づいて Criteria API を紹介します。

[Doma入門](https://qiita.com/nakamura-to/items/deaff05ba530317f3cf8) の他の記事もお読みください。

:::note warn
Doma の Criteria API には旧い Classic Criteria API と新しい Unified Criteria API の 2 種類があります。ここでは、Unified Criteria API を紹介します。
:::

## エンティティクラスの定義

次のようなデータベーススキーマがあるとします。

```sql
create table employee (
    id integer not null primary key,
    name varchar(255) not null,
    age integer not null, 
    version integer not null);
```

上記のテーブルに対応するエンティティクラスは次のように定義できます。

```java
@Entity(metamodel = @Metamodel)
public class Employee {
  @Id
  public Integer id;
  public String name;
  public Integer age;
  @Version public Integer version;
}
```

一見、通常のエンティティクラスの定義ですが、`@Entity` の宣言に `metamodel = @Metamodel` という記述があることに注目してください。この記述がすごく重要で、適切な設定でコンパイルすることでEmployeeクラスと同じパッケージに ``Employee_`` というメタモデルクラスが生成されるようになります。

# メモモデルクラスの中身

わかりやすさのために少し省略しますが、メタモデルクラスの `Employee_` は大体次のようなコードになります。

```java
public final class Employee_ implements EntityMetamodel<Employee> {
  public final PropertyMetamodel<java.lang.Integer> id = ...;
  public final PropertyMetamodel<java.lang.String> name = ...;
  public final PropertyMetamodel<java.lang.Integer> age = ...;
  public final PropertyMetamodel<java.lang.Integer> version = ...;
}
```

このメタモデルクラスのポイントは、`id`、`name`、`age`、`version` のようにエンティティクラスのプロパティと同じ名前のプロパティを持っているということです。また、エンティティクラスにおけるプロパティの型情報を持っています。

これ以降は、このメタモデルクラスと Criteria API を使って実際に SQL を組み立てる例を示します。

## Criteria API の利用

Criteria API はどこでも利用できますが、例えば Repository クラスや Doma の DAO インタフェースのデフォルトメソッドの中で利用するとわかりやすいでしょう。

**Repositoryを使う場合**
```java
public class EmployeeRepository {

  private final QueryDsl queryDsl;

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

  public Employee selectById(Integer id) {
    // メタモデルの生成
    var e = new Employee_();
    // メタモデルを使ってSQLを組み立て結果を取得
    return queryDsl.from(e).where(c -> c.eq(e.id, id)).fetchOne();
  }
}
```

**DAOを使う場合**
```java
@Dao
public interface EmployeeDao {

  default Employee selectById(Integer id) {
    // メタモデルの生成
    var e = new Employee_();
    // メタモデルを使ってSQLを組み立て結果を取得
    return QueryDsl.of(this).from(e).where(c -> c.eq(e.id, id)).fetchOne();
  }
}
```

`selectById` メソッドの中身が Criteria API の利用例です。このメソッドではメタモデルクラスと Criteria API のエントリーポイントである `QueryDsl` クラスを使ってSQLを組み立て実行結果を 1 つのエンティティとして取得しています。

組み立てられるSQLは次のようなものになります。

```sql
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.id = ?
```

型安全の観点で言うと、 WHERE 句を組み立てる `where(c -> c.eq(e.id, id))` の中の `eq` メソッドがポイントです。このメソッドはジェネリクスを活用し、第1引数の型から第2引数の型をコンパイル時に決定しています。

つまり、この例では、 `eq` メソッドの第1引数にメタモデルクラスの `Integer` 型を表すプロパティを渡すことで第2引数の型を `Integer` 型に決定しています。したがって、例えば、 `c.eq(e.id, "String value")` のように第2引数に誤った型を渡してしまうこと（結果として実行時にエラーを発生させること）を防いでいます。

## おわりに

エンティティを取得する例を使って、 Doma の Criteri API により型安全に SQL を組み立てられることを示しました。

ここで示したコードと同等のものは、下記のプロジェクトから入手できます。

* https://github.com/domaframework/getting-started/