Doma入門 - トランザクション

## はじめに

データベースアクセスに欠かせないトランザクション機能についてポイントを紹介します。

この記事では、Doma 3.6.0を前提とします。

Domaの他の機能紹介については[Doma入門](https://qiita.com/nakamura-to/items/deaff05ba530317f3cf8)もお読みください。

## 最初に検討すること

トランザクションに関して最初に検討すべきことがあります。
それは、利用しているフレームワーク（SpringFrameworkなど）がトランザクション機能を提供しているかどうかです。提供している場合、まずはそちらの利用を検討ください。

### SpringFrameworkを使う場合

SpringFrameworkはトランザクション機能を提供します。

`org.seasar.doma.jdbc.Config`の実装クラスの`getDataSource`メソッドでは、`org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy`を使ってラップした`DataSource`を返すように設定してください。**これが非常に重要です**。

上記対応を行った上で[このガイド](https://spring.io/guides/gs/managing-transactions/)を参考にSpringのコンポーネントのクラスやメソッドに`@Transactional`を付与すればトランザクションを利用できます。

[doma-spring-boot](https://github.com/domaframework/doma-spring-boot)を使えば上記の`DataSource`のラッピングは自動で行われます。doma-spring-bootを使ってSpringFrameworkのトランザクション機能を利用しているサンプルアプリとしては[spring-boot-jpetstore](https://github.com/domaframework/spring-boot-jpetstore)があります。

### Quarkusを使う場合

Quarkusはトランザクション機能を提供します。

`org.seasar.doma.jdbc.Config`の実装クラスの`getDataSource`メソッドでは、Quarkusのコネクションプール実装である[Agroal](https://quarkus.io/guides/datasource)で管理された`DataSource`を返してください。

上記対応を行った上で[このドキュメント](https://quarkus.io/guides/transaction)にあるようにCDIのコンポーネントのクラスやメソッドに`@Transactional`を付与すればトランザクションを利用できます。

[Quarkus Doma Extension](https://github.com/quarkiverse/quarkiverse-doma)を使えば上記の`DataSource`の設定は自動で行われます。Quarkus Doma Extensionを使ってQuarkusのトランザクション機能を利用しているサンプルアプリとしては[quarkus-sample](https://github.com/domaframework/quarkus-sample)があります。

### トランザクション機能を提供するフレームワークを使わない場合

Domaのローカルトランザクションの利用を検討してください。

Domaのローカルトランザクションの特徴は次の通りです。

* `ThreadLocal`を使ってスレッドごとにコネクションを管理する
* 名前の通りローカルトランザクションなので扱えるリソースは1つだけ（グローバルトランザクションのように2フェーズコミットの機能はない）
* 手続的なAPIである`org.seasar.doma.jdbc.tx.TransactionManager`を提供する（`@Transactional`のような宣言的なAPIではない）
* JDBCのセーブポイント機能を提供する

使い方は次の節で述べます。

## Domaのローカルトランザクション

動くコードは[getting-started](https://github.com/domaframework/getting-started)にありますが、ここでは重要な部分を抜粋して示します。

```java:Main.java
public class Main {

  public static void main(String[] args) {
    var config =
        SimpleConfig.builder("jdbc:h2:mem:tutorial;DB_CLOSE_DELAY=-1", "sa", null)
            .jdbcLogger(new Slf4jJdbcLogger())
            .build();
    var tm = config.getTransactionManager();

    // setup database
    var appDao = new AppDaoImpl(config);
    tm.required(appDao::create);

    // read and update
    tm.required(
        () -> {
          var repository = new EmployeeRepository(config);
          var employee = repository.selectById(1);
          employee.age += 1;
          repository.update(employee);
        });
  }
}
```

Domaのローカルトランザクションを使う場合、`SimpleConfig`を使って`Config`を生成することをお勧めします。`SimpleConfig`を使えば、ローカルトランザクションを利用するための設定が自動で行われます。

`config.getTransactionManager()`で取得できる`tm`は`LocalTransactionManager`のインスタンスです。`tm`の`required`メソッドにトランザクション内で扱いたい処理をラムダ式で渡すことでトランザクションを実行できます。

`required`メソッドはトランザクションがまだ開始されていなかったら開始するメソッドで、他に常に新規にトランザクションを開始する`requiresNew`メソッドやトランザクションを一旦停止する`notSupported`メソッドなどがあります。これらのメソッドはネストして使えます。

ラムダ式から例外をスローするか`setRollbackOnly`メソッドを呼び出すかするとトランザクションはロールバックされます。それ以外ではコミットされます。


## おわりに

Domaでトランザクションを利用するポイントを紹介しました。

Domaを使っていてトランザクションがうまく動いていないなと思ったら、この記事をはじめリンク先の記事やサンプルも参考にしてもらえればと思います。