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が呼ばれるので、そちらでも良い。