はじめに
データベースアクセスに欠かせないトランザクション機能についてポイントを紹介します。
この記事では、Doma 2.44.0を前提とします。
Domaの他の機能紹介についてはDoma入門もお読みください。
最初に検討すること
トランザクションに関して最初に検討すべきことがあります。
それは、利用しているフレームワーク(SpringFrameworkなど)がトランザクション機能を提供しているかどうかです。提供している場合、まずはそちらの利用を検討ください。
SpringFrameworkを使う場合
SpringFrameworkはトランザクション機能を提供します。
org.seasar.doma.jdbc.Config
の実装クラスのgetDataSource
メソッドでは、org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
を使ってラップしたDataSource
を返すように設定してください。これが非常に重要です。
上記対応を行った上でこのガイドを参考にSpringのコンポーネントのクラスやメソッドに@Transactional
を付与すればトランザクションを利用できます。
doma-spring-bootを使えば上記のDataSource
のラッピングは自動で行われます。doma-spring-bootを使ってSpringFrameworkのトランザクション機能を利用しているサンプルアプリとしてはspring-boot-jpetstoreがあります。
Quarkusを使う場合
Quarkusはトランザクション機能を提供します。
org.seasar.doma.jdbc.Config
の実装クラスのgetDataSource
メソッドでは、Quarkusのコネクションプール実装であるAgroalで管理されたDataSource
を返してください。
上記対応を行った上でこのドキュメントにあるようにCDIのコンポーネントのクラスやメソッドに@Transactional
を付与すればトランザクションを利用できます。
Quarkus Doma Extensionを使えば上記のDataSource
の設定は自動で行われます。Quarkus Doma Extensionを使ってQuarkusのトランザクション機能を利用しているサンプルアプリとしてはquarkus-sampleがあります。
トランザクション機能を提供するフレームワークを使わない場合
Domaのローカルトランザクションの利用を検討してください。
Domaのローカルトランザクションの特徴は次の通りです。
-
ThreadLocal
を使ってスレッドごとにコネクションを管理する - 名前の通りローカルトランザクションなので扱えるリソースは1つだけ(グローバルトランザクションのように2フェーズコミットの機能はない)
- 手続的なAPIである
org.seasar.doma.jdbc.tx.TransactionManager
を提供する(@Transactional
のような宣言的なAPIではない) - JDBCのセーブポイント機能を提供する
使い方は次の節で述べます。
Domaのローカルトランザクション
動くコードはgetting-startedにありますが、ここでは重要な部分を抜粋して示します。
public class Main {
public static void main(String[] args) {
var config = createConfig();
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);
});
}
private static Config createConfig() {
var dialect = new H2Dialect();
// ①トランザクション対応のデータソースを作る
var dataSource =
new LocalTransactionDataSource("jdbc:h2:mem:tutorial;DB_CLOSE_DELAY=-1", "sa", null);
var jdbcLogger = new Slf4jJdbcLogger();
// ②トランザクションマネージャーを作る
var transactionManager = new LocalTransactionManager(dataSource, jdbcLogger);
// ③Configの実装クラスから上記の①や②で作ったインスタンスを返せるようにする
return new DbConfig(dialect, dataSource, jdbcLogger, transactionManager);
}
}
①トランザクション対応のデータソースを作る
LocalTransactionDataSource
をインスタンス化します。
この例では接続URLなどをコンストラクタで受け取っていますが、DataSource
インスタンスを受け取るコンストラクタも持っています。
②トランザクションマネージャーを作る
上記の①で作ったLocalTransactionDataSource
のインスタンスをコンストラクタに渡してLocalTransactionManager
をインスタンス化します。
③Configの実装クラスから上記の①や②で作ったインスタンスを返せるようにする
①と②で作ったインスタンスをConfig
の実装クラスであるDbConfig
のコンストラクタに渡してインスタンス化します。
④トランザクションマネージャーのメソッドにラムダ式を渡す
ここのtm
は②で作ったLocalTransactionManager
のインスタンスです。
tm
のrequired
メソッドにトランザクション内で扱いたい処理をラムダ式で渡すことでトランザクションを実行できます。
required
メソッドはトランザクションがまだ開始されていなかったら開始するメソッドで、他に常に新規にトランザクションを開始するrequiresNew
メソッドやトランザクションを一旦停止するnotSupported
メソッドなどがあります。これらのメソッドはネストして使えます。
ラムダ式から例外をスローするかsetRollbackOnly
メソッドを呼び出すかするとトランザクションはロールバックされます。それ以外ではコミットされます。
1つ注意点ですが、ローカルトランザクションの設定をした場合、Domaによる全てのデータベースアクセスは原則的にTransactionManager
経由で行う必要があります。そうしない場合、例外が発生します。
おわりに
Domaでトランザクションを利用するポイントを紹介しました。
Domaを使っていてトランザクションがうまく動いていないなと思ったら、この記事をはじめリンク先の記事やサンプルも参考にしてもらえればと思います。