基本的にフレームワークがトランザクションを管理している。
httpリクエスト毎にトランザクションを開始し、レスポンスを返すときにコミットされる。
例外で終わったときには自動的にロールバックされる。
play1.3で複数DB接続をサポートした影響で、トランザクション制御に影響が出ている。
分かる範囲でそれも追記しておく。
アノテーション
DBの更新を行わないとき、以下のアノテーションを該当のメソッドに設定することで読み出し専用にすることができる。
@play.db.jpa.Transactional(readOnly=true)
トランザクションの自動開始をさせたくないときは、以下のアノテーションを該当メソッド、もしくは該当コントローラクラスに設定する。
@play.db.jpa.NoTransaction
DBアクセスが不要な場合、積極的に利用すると性能アップにつながる。
トランザクションの開始
第1引数はDB識別名、第2引数はreadOnlyなトランザクションかどうかを指定する。
既にトランザクションを開始している場合は、そのトランザクションは破棄してから新たにトランザクションを開始する。
play.db.jpa.JPA.startTx(String, boolean)
デフォルトのDB識別名はJPAクラスに定義してある定数を使って指定する方法が妥当かと。
JPA.startTx(JPA.DEFAULT, false);
ちなみにplay1.3より前はJPAPlugin.startTx
を使って下記のような記述をしていた。現在は非推奨。
JPAPlugin.startTx(false);
コミット
トランザクションの開始と同じく、DB識別名を指定して操作する。
JPA.closeTx(JPA.DEFAULT);
play1.3より前は、JPAPlugin.closeTx
を使用していた。引数はtrueならロールバックする。
JPAPlugin.closeTx(false);
ロールバック
JPA.rollbackTx(JPA.DEFAULT);
play1.3より前の場合は、下記のような2通りの記述方法。
JPA.setRollbackOnly();
JPAPlugin.closeTx(true);
並列トランザクションを扱う場合(play1.3以降)
幾つかのトランザクションを並列に扱うときは、カレントのトランザクション退避してつつ2つ目のトランザクションを開始して切り替えることで実現することになる。
playframework1としては1リクエスト1トランザクションを基本としているので、リクエスト処理中に例外が発生したときはトランザクションに対してロールバックが行われるようになっている。
しかしそれはカレントのトランザクションであって、退避中のトランザクションについてはコミットもロールバックもしてくれない。@Finally
などで必ずトランザクションが閉じれるように実装すべき。
対処を忘れるといずれ開けるトランザクションが枯渇してエラーで落ちる。
退避
// コンテキスト退避
JPAContext defContext = JPA.currentEntityManager.get().get(JPA.DEFAULT);
JPA.currentEntityManager.get().remove(JPA.DEFAULT);
// 新たなトランザクションを開始
JPA.startTx(JPA.DEFAULT, false);
復帰
// トランザクションを閉じる
JPA.closeTx(JPA.DEFAULT);
// コンテキスト復帰
if (defContext != null) {
JPA.currentEntityManager.get().put(JPA.DEFAULT, defContext);
}
※退避コンテキストがNULLのときは復帰しなくていい。
並列トランザクションを扱う場合(play1.3より前)
対象DBが1つのためトランザクションはJPA.local.get()
, .set()
でアクセスすることができる。
例外発生によるロールバックについてはplay1.3以降と同じく注意が必要。
退避
// カレントのトランザクションをtempContextに退避。カレントトランザクションは無効にしておく。
JPA tran1 = JPA.local.get();
JPA.local.remove();
復帰
// 退避していたトランザクションを、カレントトランザクションに設定する。
JPA.local.set(tran1);
参考
blog.k11i.biz: Play framework でトランザクションを複数並列に扱うには?
http://blog.k11i.biz/2012/01/play-framework.html
トランザクションの閉じ忘れ
トランザクションを閉じ忘れが原因のエラー例。”Connection is not available” 例外が発生している様子が分かる。
13 Feb 2021 20:06:39,184 WARN ~ SQL Error: 0, SQLState: null 13 Feb 2021 20:06:39,184 ERROR ~ HikariPool-1 - Connection is not available, request timed out after 5254ms. Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 5254ms. at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:676) at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:190) at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:155) at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128) at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.java:35) at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:106) … 54 more