S-JIS[2023-12-09/2024-02-17]
TsurugiのIceaxe(Javaライブラリー)のTsurugiTransactionのメモ。
|
|
IceaxeのTsurugiTransactionは、SQLを実行してコミット/ロールバックするクラス。
概ね、TsubakuroのSqlClientのTransactionクラスのラッパー。
TsurugiTransactionはTsurugiSessionから生成する。
TsurugiTransactionは使い終わったらクローズする必要がある。(コミット/ロールバックせずにクローズすると、ロールバック扱いになる)
TsurugiTransactionはTsurugiTransactionManagerで生成することも出来る。
TsurugiTransactionManagerを使うと、TsurugiTransactionのライフサイクルがその中で管理される。(TsurugiTransactionの生成・コミット/ロールバックが自動的に行われる)
また、TsurugiTransactionManagerは、シリアライゼーションエラー発生時に自動的にトランザクションを再実行(リトライ)する機能を持っている。
import java.util.concurrent.TimeUnit; import com.tsurugidb.iceaxe.transaction.TgCommitType; import com.tsurugidb.iceaxe.transaction.TsurugiTransaction; import com.tsurugidb.iceaxe.transaction.option.TgTxOption;
// トランザクション開始 var txOption = TgTxOption.ofOCC(); try (TsurugiTransaction transaction = session.createTransaction(txOption)) { // SQL実行 String sql = "update test set value = value + 1"; try (var ps = session.createStatement(sql)) { transaction.executeAndGetCount(ps); } // コミット transaction.setCommitTimeout(10, TimeUnit.SECONDS); transaction.commit(TgCommitType.DEFAULT); // transaction.rollback(); // ロールバック }
TsurugiTransactionはTsurugiSessionのcreateTransactionメソッドで生成する。
生成時にトランザクションオプションを指定する。
→TgTxOptionの例
メソッド | 説明 | 例 |
---|---|---|
executeDdl | DDLを実行する。 | transaction.executeDdl("drop table test"); |
メソッド | 説明 | 例 |
---|---|---|
executeQuery | select文を実行する。 TsurugiQueryResultが返る。 (TsurugiQueryResultはクローズする必要がある) |
try (var ps = session.createQuery("select * from
test")) { |
executeAndForEach | select文を実行する。 レコードを処理する関数を渡す。 |
var builder = Stream.<TsurugiResultEntity>builder(); |
executeAndGetList | select文を実行し、レコードのListを返す。 | List<TsurugiResultEntity> entityList = transaction.executeAndGetList(ps); |
executeAndFindRecord | select文を実行し、先頭1レコードを返す。 | Optional<TsurugiResultEntity> entity = transaction.executeAndFindRecord(ps); |
メソッド | 説明 | 例 |
---|---|---|
executeStatement | 更新系SQLを実行する。 TsurugiStatementResultが返る。 (TsurugiStatementResultはクローズする必要がある) |
try (var ps = session.createStatement("update test set bar = 999")) { |
executeBatch | ひとつのSQLに複数レコード分のパラメーターを渡してまとめて実行する。 Tsurugi 1.0.0-BETA1では未実装。 |
|
executeAndGetCount | 更新系SQLを実行する。 処理件数が返る。 (Iceaxe 1.0.1(Tsurugi 1.0.0-BETA1)では常に-1が返るが、1.1.0(1.0.0-BETA2)以降では正しく処理件数が返る) |
int count = transaction.executeAndGetCount(ps); |
executeAndGetCountDetail | 更新系SQLを実行する。 実行したSQLの種類毎の処理件数明細が返る。 Iceaxe 1.1.0(Tsurugi 1.0.0-BETA2)以降。 |
TgResultCount count = transaction.executeAndGetCountDetail(ps); |
メソッド | 説明 | 例 |
---|---|---|
available | トランザクションが使用可能(Tsurugiサーバーと通信可能)かどうかを返す。 | boolean b = transaction.available(); |
getTransactionStatus | トランザクションの状態を取得する。 SQL実行がエラーになった場合、そのトランザクションはinactiveになる。 当メソッドで、inactiveになった原因のエラーを取得できる。 |
TgTxStatus status = transaction.getTransactionStatus(); |
insert文を実行する際は、バインド変数を使って、テーブルの1レコード分のデータを保持するEntityクラスを引数に使用することが出来る。
import com.tsurugidb.iceaxe.sql.parameter.TgParameterMapping;
String sql = "insert into test (foo, bar, zzz) values(:foo, :bar, :zzz)"; var parameterMapping = TgParameterMapping.of(TestEntity.class) .addInt("foo", TestEntity::getFoo) .addLong("bar", TestEntity::getBar) .addString("zzz", TestEntity::getZzz); try (var ps = session.createStatement(sql, parameterMapping)) { var tm = session.createTransactionManager(〜); tm.execute(transaction -> { var entity = new TestEntity(); entity.setFoo(123); entity.setBar(456L); entity.setZzz("abc"); transaction.executeAndGetCountDetail(ps, entity); }); }
バインド変数の変数名を1か所でまとめて定義したい場合は、以下のようにすることも出来る。
import com.tsurugidb.iceaxe.sql.parameter.TgBindVariable; import com.tsurugidb.iceaxe.sql.parameter.TgBindVariables; import com.tsurugidb.iceaxe.sql.parameter.TgParameterMapping;
var foo = TgBindVariable.ofInt("foo"); var bar = TgBindVariable.ofLong("bar"); var zzz = TgBindVariable.ofString("zzz"); String sql = "insert into test (foo, bar, zzz) values(" + TgBindVariables.toSqlNames(foo, bar, zzz) + ")"; var parameterMapping = TgParameterMapping.of(TestEntity.class) .add(foo, TestEntity::getFoo) .add(bar, TestEntity::getBar) .add(zzz, TestEntity::getZzz); try (var ps = session.createStatement(sql, parameterMapping)) { var tm = session.createTransactionManager(〜); tm.execute(transaction -> { var entity = new TestEntity(); entity.setFoo(123); entity.setBar(456L); entity.setZzz("abc"); transaction.executeAndGetCountDetail(ps, entity); }); }
select文を実行する際、デフォルトでは、1レコード分のデータはTsurugiResultEntityというクラスに入ってくる。
これを、ユーザーが定義したEntityクラスにすることが出来る。
import com.tsurugidb.iceaxe.sql.result.TgResultMapping;
String sql = "select foo, bar, zzz from test"; var resultMapping = TgResultMapping.of(TestEntity::new) .addInt("foo", TestEntity::setFoo) .addLong("bar", TestEntity::setBar) .addString("zzz", TestEntity::setZzz); try (var ps = session.createQuery(sql, resultMapping)) { var tm = session.createTransactionManager(〜); tm.execute(transaction -> { List<TestEntity> entityList = transaction.executeAndGetList(ps); }); }
以下のように2つのトランザクションをtx1→tx2の順で作った場合でも、tx2の方がtx1より先に開始されることがある。[2024-02-17]
var txOption = TgTxOption.ofLTX(); try (TsurugiTransaction tx1 = session.createTransaction(txOption); TsurugiTransaction tx2 = session.createTransaction(txOption)) { 〜 }
IceaxeのcreateTransaction内部では、TsubakuroのcreateTransactionを呼んでFutureResponse<Transaction>を取得するが、必要になるまでFutureResponseからgetしない。(遅延評価する)
そのため、タイミングによっては、後からcreateTransactionした側が先に開始されることがありうる。
確実に開始順序を制御したい場合は、Tsubakuro(Iceaxe内部)のFutureResponse<Transaction>をgetしてから次のトランザクションを開始する必要がある。
var txOption = TgTxOption.ofLTX(); try (TsurugiTransaction tx1 = session.createTransaction(txOption)) { // tx1.getLowTransaction(); tx1.getTransactionId(); try (TsurugiTransaction tx2 = session.createTransaction(txOption)) { 〜 } }
getLowTransactionを呼ぶとFutureResponse<Transaction>からgetされる。
ただ、getLowTransactionはIceaxe内部用メソッドなので、他のメソッドを使う方がいいかもしれない。
getTransactionIdやSQLを実行するexecute系メソッドでも内部でgetLowTransactionが呼ばれるので、そちらでも良い。