S-JIS[2023-12-09/2024-02-17]

Iceaxe TsurugiTransaction

TsurugiIceaxe(Javaライブラリー)のTsurugiTransactionのメモ。


概要

IceaxeのTsurugiTransactionは、SQLを実行してコミット/ロールバックするクラス。
概ね、TsubakuroSqlClientTransactionクラスのラッパー。

TsurugiTransactionはTsurugiSessionから生成する。
TsurugiTransactionは使い終わったらクローズする必要がある。(コミット/ロールバックせずにクローズすると、ロールバック扱いになる)


TsurugiTransactionはTsurugiTransactionManagerで生成することも出来る。

TsurugiTransactionManagerを使うと、TsurugiTransactionのライフサイクルがその中で管理される。(TsurugiTransactionの生成・コミット/ロールバックが自動的に行われる)
また、TsurugiTransactionManagerは、シリアライゼーションエラー発生時に自動的にトランザクションを再実行(リトライ)する機能を持っている。


TsurugiTransactionを生成・使用する例

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の例


TsurugiTransactionのメソッド

DDLの実行

メソッド 説明
executeDdl DDLを実行する。 transaction.executeDdl("drop table test");

selectの実行

メソッド 説明
executeQuery select文を実行する。
TsurugiQueryResultが返る。
(TsurugiQueryResultはクローズする必要がある)
try (var ps = session.createQuery("select * from test")) {
  try (var result = transaction.executeQuery(ps)) {
    List<TsurugiResultEntity> entityList = result.getRecordList();
  }
}
executeAndForEach select文を実行する。
レコードを処理する関数を渡す。
var builder = Stream.<TsurugiResultEntity>builder();
transaction.executeAndForEach(ps, builder::add);
Stream<TsurugiResultEntity> stream = builder.build();
executeAndGetList select文を実行し、レコードのListを返す。 List<TsurugiResultEntity> entityList = transaction.executeAndGetList(ps);
executeAndFindRecord select文を実行し、先頭1レコードを返す。 Optional<TsurugiResultEntity> entity = transaction.executeAndFindRecord(ps);

更新系SQLの実行

メソッド 説明
executeStatement 更新系SQLを実行する。
TsurugiStatementResultが返る。
(TsurugiStatementResultはクローズする必要がある)
try (var ps = session.createStatement("update test set bar = 999")) {
  try (var result = transaction.executeStatement(ps)) {
    TgResultCount count = result.getCountDetail();
  }
}
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の例

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の例

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


Iceaxeへ戻る / Tsurugiへ戻る / 技術メモへ戻る
メールの送信先:ひしだま