Java 用 Amazon QLDB ドライバー — クックブックリファレンス - Amazon Quantum Ledger Database (Amazon QLDB)

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

Java 用 Amazon QLDB ドライバー — クックブックリファレンス

このリファレンスガイドでは、Java 用 Amazon QLDB ドライバーの一般的なユースケースについて説明します。Java コード例を提供し、ドライバーを使用して作成、読み取り、更新、および削除 (CRUD) の基本的なオペレーションを実行する方法を説明しています。Amazon Ion データを処理するためのコード例も含まれています。さらに、このガイドでは、トランザクションをべき等にし、一意性の制約を実装するためのベストプラクティスを取り上げています。

注記

該当する場合、一部のユースケースでは、サポートされているメジャーバージョンの Java 用 QLDB ドライバーに対して異なるコード例があります。

ドライバーのインポート

次のコード例では、ドライバー、QLDB セッションクライアント、Amazon Ion パッケージ、およびその他の関連する依存関係をインポートします。

2.x
import com.amazon.ion.IonStruct; import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import software.amazon.awssdk.services.qldbsession.QldbSessionClient; import software.amazon.qldb.QldbDriver; import software.amazon.qldb.Result;
1.x
import com.amazon.ion.IonStruct; import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import com.amazonaws.services.qldbsession.AmazonQLDBSessionClientBuilder; import software.amazon.qldb.PooledQldbDriver; import software.amazon.qldb.Result;

ドライバーのインスタンス化

次のコード例では、指定された台帳名に接続するドライバーインスタンスを作成し、指定された再試行ロジックをカスタム再試行制限で使用します。

注記

この例では、Amazon Ion システムオブジェクト (IonSystem) もインスタンス化します。このリファレンスでいくつかのデータオペレーションを実行するときに Ion データを処理するには、このオブジェクトが必要です。詳細については、「Amazon Ion の操作」を参照してください。

2.x
QldbDriver qldbDriver = QldbDriver.builder() .ledger("vehicle-registration") .transactionRetryPolicy(RetryPolicy .builder() .maxRetries(3) .build()) .sessionClientBuilder(QldbSessionClient.builder()) .build(); IonSystem SYSTEM = IonSystemBuilder.standard().build();
1.x
PooledQldbDriver qldbDriver = PooledQldbDriver.builder() .withLedger("vehicle-registration") .withRetryLimit(3) .withSessionClientBuilder(AmazonQLDBSessionClientBuilder.standard()) .build(); IonSystem SYSTEM = IonSystemBuilder.standard().build();

CRUD オペレーション

QLDB は作成、読み取り、更新、および削除 (CRUD) オペレーションをトランザクションの一部として実行します。

警告

ベストプラクティスとして、書き込みトランザクションを厳密にべき等にしてください。

トランザクションをべき等にする

再試行の場合に予期しない結果を避けるために、書き込みトランザクションをべき等にすることをお勧めします。トランザクションは、複数回実行して毎回同じ結果を生成できる場合、idempotent です。

例えば、Person という名前のテーブルにドキュメントを挿入するトランザクションについて考えてみます。トランザクションは、まずドキュメントがテーブル内に既に存在するかどうかをチェックする必要があります。このチェックを行わないと、テーブルに重複するドキュメントができる可能性があります。

QLDB がサーバー側でトランザクションを正常にコミットできるが、レスポンスを待機中にクライアントはタイムアウトするとします。トランザクションがべき等でない場合、再試行時に同じドキュメントが複数回挿入される可能性があります。

インデックスを使用してテーブル全体のスキャンを回避する

インデックス付きフィールドまたはドキュメント ID で等価演算子を使用する WHERE 述語句でステートメントを実行する (例: WHERE indexedField = 123 または WHERE indexedField IN (456, 789)) こともお勧めします。このインデックス付きのルックアップがない場合、QLDB はテーブルスキャンを実行する必要があり、トランザクションタイムアウトやオプティミスティック同時実行制御 (OCC) 競合が発生する可能性があります。

OCC の詳細については、「Amazon QLDB 同時実行モデル」を参照してください。

暗黙的に作成されたトランザクション

QldbDriver.execute メソッドは、Executor のインスタンスを受け取る Lambda 関数を受け入れます。これを使用してステートメントを実行できます。Executor インスタンスは、暗黙的に作成されたトランザクションをラップします。

Executor.execute メソッドを使用して、Lambda 関数内でステートメントを実行できます。ドライバーは、Lambda 関数が戻ったときに暗黙的にトランザクションをコミットします。

次のセクションでは、基本的な CRUD オペレーションの実行、カスタム再試行ロジックの指定、一意性制約の実装方法について説明します。

注記

該当する場合、これらのセクションでは、組み込みの Ion ライブラリと Jackson Ion マッパーライブラリの両方を使用して Amazon Ion データを処理するコード例を示します。詳細については、「Amazon Ion の操作」を参照してください。

テーブルの作成

qldbDriver.execute(txn -> { txn.execute("CREATE TABLE Person"); });

インデックスの作成

qldbDriver.execute(txn -> { txn.execute("CREATE INDEX ON Person(GovId)"); });

ドキュメントの読み取り

// Assumes that Person table has documents as follows: // { GovId: "TOYENC486FH", FirstName: "Brent" } qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'"); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });

クエリパラメータの使用

次のコード例では、Ion 型のクエリパラメータを使用します。

qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });

次のコード例では、複数のクエリパラメータを使用します。

qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", SYSTEM.newString("TOYENC486FH"), SYSTEM.newString("Brent")); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });

次のコード例では、クエリパラメータのリストを使用します。

qldbDriver.execute(txn -> { final List<IonValue> parameters = new ArrayList<>(); parameters.add(SYSTEM.newString("TOYENC486FH")); parameters.add(SYSTEM.newString("ROEE1")); parameters.add(SYSTEM.newString("YH844")); Result result = txn.execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", parameters); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });
// Assumes that Person table has documents as follows: // {GovId: "TOYENC486FH", FirstName: "Brent" } qldbDriver.execute(txn -> { try { Result result = txn.execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'"); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });

クエリパラメータの使用

次のコード例では、Ion 型のクエリパラメータを使用します。

qldbDriver.execute(txn -> { try { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", MAPPER.writeValueAsIonValue("TOYENC486FH")); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });

次のコード例では、複数のクエリパラメータを使用します。

qldbDriver.execute(txn -> { try { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", MAPPER.writeValueAsIonValue("TOYENC486FH"), MAPPER.writeValueAsIonValue("Brent")); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });

次のコード例では、クエリパラメータのリストを使用します。

qldbDriver.execute(txn -> { try { final List<IonValue> parameters = new ArrayList<>(); parameters.add(MAPPER.writeValueAsIonValue("TOYENC486FH")); parameters.add(MAPPER.writeValueAsIonValue("ROEE1")); parameters.add(MAPPER.writeValueAsIonValue("YH844")); Result result = txn.execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", parameters); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });
注記

インデックス付きルックアップなしでクエリを実行すると、完全なテーブルスキャンが呼び出されます。この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、クエリのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

ドキュメントの挿入

次のコード例では、Ion データ型を挿入します。

qldbDriver.execute(txn -> { // Check if a document with GovId:TOYENC486FH exists // This is critical to make this transaction idempotent Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); // Check if there is a result if (!result.iterator().hasNext()) { IonStruct person = SYSTEM.newEmptyStruct(); person.put("GovId").newString("TOYENC486FH"); person.put("FirstName").newString("Brent"); // Insert the document txn.execute("INSERT INTO Person ?", person); } });

次のコード例では、Ion データ型を挿入します。

qldbDriver.execute(txn -> { try { // Check if a document with GovId:TOYENC486FH exists // This is critical to make this transaction idempotent Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", MAPPER.writeValueAsIonValue("TOYENC486FH")); // Check if there is a result if (!result.iterator().hasNext()) { // Insert the document txn.execute("INSERT INTO Person ?", MAPPER.writeValueAsIonValue(new Person("Brent", "TOYENC486FH"))); } } catch (IOException e) { e.printStackTrace(); } });

このトランザクションは、ドキュメントを Person テーブルに挿入します。挿入する前に、まずドキュメントがテーブル内に既に存在しているかどうかをチェックします。このチェックにより、トランザクションは本質的にべき等になります。このトランザクションを複数回実行しても、意図しない副作用は発生しません。

注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

1 つのステートメントで複数のドキュメントの挿入

1 つの INSERT ステートメントを使用して複数のドキュメントを挿入するために、次のように型 IonList のパラメータを (明示的に IonValue にキャストして) ステートメントに渡すことができます。

// people is an IonList explicitly cast as an IonValue txn.execute("INSERT INTO People ?", (IonValue) people);

IonList を渡すときは、変数プレースホルダー (?) を二重山括弧 (<<...>>) で囲まないでください。マニュアルの PartiQL ステートメントでは、二重山括弧はバッグと呼ばれる順序付けされていないコレクションを表します。

TransactionExecutor.execute メソッドはオーバーロードされています。これは、可変数の IonValue 引数 (varargs)、または単一の List<IonValue> 引数を受け入れます。ion-java では、IonListList<IonValue> として実装されます。

Java では、オーバーロードされたメソッドを呼び出すと、デフォルトで最も具体的なメソッド実装が使用されます。この場合、IonList パラメータを渡すと、List<IonValue> を使用するメソッドがデフォルトで使用されます。このメソッド実装は、呼び出されるとリストの IonValue 要素を個別の値として渡します。したがって、代わりに varargs メソッドを呼び出すには、IonList パラメータを IonValue に明示的にキャストする必要があります。

ドキュメントの更新

qldbDriver.execute(txn -> { final List<IonValue> parameters = new ArrayList<>(); parameters.add(SYSTEM.newString("John")); parameters.add(SYSTEM.newString("TOYENC486FH")); txn.execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", parameters); });
qldbDriver.execute(txn -> { try { final List<IonValue> parameters = new ArrayList<>(); parameters.add(MAPPER.writeValueAsIonValue("John")); parameters.add(MAPPER.writeValueAsIonValue("TOYENC486FH")); txn.execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", parameters); } catch (IOException e) { e.printStackTrace(); } });
注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

ドキュメントの削除

qldbDriver.execute(txn -> { txn.execute("DELETE FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); });
qldbDriver.execute(txn -> { try { txn.execute("DELETE FROM Person WHERE GovId = ?", MAPPER.writeValueAsIonValue("TOYENC486FH")); } catch (IOException e) { e.printStackTrace(); } });
注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

トランザクションで複数のステートメントの実行

// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. public static boolean InsureCar(QldbDriver qldbDriver, final String vin) { final IonSystem ionSystem = IonSystemBuilder.standard().build(); final IonString ionVin = ionSystem.newString(vin); return qldbDriver.execute(txn -> { Result result = txn.execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", ionVin); if (!result.isEmpty()) { txn.execute("UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin); return true; } return false; }); }

再試行ロジック

ドライバーの execute メソッドには、再試行可能な例外 (タイムアウトや OCC 競合など) が発生した場合にトランザクションを再試行する組み込みの再試行メカニズムがあります。

2.x

再試行の最大数とバックオフ戦略を設定できます。

デフォルトの再試行制限は 4 であり、デフォルトのバックオフ戦略は DefaultQldbTransactionBackoffStrategy です。再試行設定は、RetryPolicy のインスタンスを使用して、ドライバーインスタンスごとおよびトランザクションごとに設定できます。

次のコード例では、ドライバーのインスタンスのカスタム再試行制限とカスタムバックオフ戦略を使用して再試行ロジックを指定します。

public void retry() { QldbDriver qldbDriver = QldbDriver.builder() .ledger("vehicle-registration") .transactionRetryPolicy(RetryPolicy.builder() .maxRetries(2) .backoffStrategy(new CustomBackOffStrategy()).build()) .sessionClientBuilder(QldbSessionClient.builder()) .build(); } private class CustomBackOffStrategy implements BackoffStrategy { @Override public Duration calculateDelay(RetryPolicyContext retryPolicyContext) { return Duration.ofMillis(1000 * retryPolicyContext.retriesAttempted()); } }

次のコード例では、特定のトランザクションのカスタム再試行制限とカスタムバックオフ戦略を使用して再試行ロジックを指定します。execute のこの設定は、ドライバーインスタンスに設定された再試行ロジックを上書きします。

public void retry() { Result result = qldbDriver.execute(txn -> { txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); }, RetryPolicy.builder() .maxRetries(2) .backoffStrategy(new CustomBackOffStrategy()) .build()); } private class CustomBackOffStrategy implements BackoffStrategy { // Configuring a custom backoff which increases delay by 1s for each attempt. @Override public Duration calculateDelay(RetryPolicyContext retryPolicyContext) { return Duration.ofMillis(1000 * retryPolicyContext.retriesAttempted()); } }
1.x

再試行の最大数は設定可能です。再試行制限を設定するには、PooledQldbDriver の初期化時に retryLimit プロパティを設定します。

デフォルトの再試行制限回数は 4 です。

一意性制約の実装

QLDB はユニークインデックスをサポートしていませんが、この動作をアプリケーションに実装できます。

Person テーブル内の GovId フィールドに対して一意性制約を実装するとします。これを行うには、以下を実行するトランザクションを記述します。

  1. テーブルに指定された GovId を持つ既存のドキュメントがないことをアサートします。

  2. アサーションに合格した場合は、ドキュメントを挿入します。

競合するトランザクションが同時にアサーションに合格すると、一方のトランザクションだけが正常にコミットされます。もう一方のトランザクションは OCC 競合例外で失敗します。

次のコード例は、この一意性制約ロジックを実装する方法を示しています。

qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); // Check if there is a result if (!result.iterator().hasNext()) { IonStruct person = SYSTEM.newEmptyStruct(); person.put("GovId").newString("TOYENC486FH"); person.put("FirstName").newString("Brent"); // Insert the document txn.execute("INSERT INTO Person ?", person); } });
注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

Amazon Ion の操作

QLDB で Amazon Ion データを処理するには、複数の方法があります。Ion ライブラリから組み込みメソッドを使用するには、必要に応じて、ドキュメントを柔軟に作成および変更します。または、FasterXML の Ion 用 Jackson データフォーマットモジュールを使用して Ion ドキュメントを Plain Old Java Object (POJO) モデルにマップすることもできます。

次のセクションでは、両方の手法を使用して Ion データを処理するコード例について説明します。

Ion パッケージのインポート

アーティファクト ion-java を Java プロジェクトの依存関係として追加します。

Gradle
dependencies { compile group: 'com.amazon.ion', name: 'ion-java', version: '1.6.1' }
Maven
<dependencies> <dependency> <groupId>com.amazon.ion</groupId> <artifactId>ion-java</artifactId> <version>1.6.1</version> </dependency> </dependencies>

次の Ion パッケージをインポートします。

import com.amazon.ion.IonStruct; import com.amazon.ion.IonSystem; import com.amazon.ion.system.IonSystemBuilder;

アーティファクト jackson-dataformat-ion を Java プロジェクトの依存関係として追加します。QLDB にはバージョン 2.10.0 以降が必要です。

Gradle
dependencies { compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-ion', version: '2.10.0' }
Maven
<dependencies> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-ion</artifactId> <version>2.10.0</version> </dependency> </dependencies>

次の Ion パッケージをインポートします。

import com.amazon.ion.IonReader; import com.amazon.ion.IonStruct; import com.amazon.ion.system.IonReaderBuilder; import com.amazon.ion.system.IonSystemBuilder; import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; import com.fasterxml.jackson.dataformat.ion.ionvalue.IonValueMapper;

Ion の初期化

IonSystem SYSTEM = IonSystemBuilder.standard().build();
IonObjectMapper MAPPER = new IonValueMapper(IonSystemBuilder.standard().build());

Ion オブジェクトの作成

次のコード例では、IonStruct インターフェイスとその組み込みメソッドを使用して Ion オブジェクトを作成します。

IonStruct ionStruct = SYSTEM.newEmptyStruct(); ionStruct.put("GovId").newString("TOYENC486FH"); ionStruct.put("FirstName").newString("Brent"); System.out.println(ionStruct.toPrettyString()); // prints a nicely formatted copy of ionStruct

以下のような Person という名前の JSON にマップされたモデルクラスがあるとします。

import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public static class Person { private final String firstName; private final String govId; @JsonCreator public Person(@JsonProperty("FirstName") final String firstName, @JsonProperty("GovId") final String govId) { this.firstName = firstName; this.govId = govId; } @JsonProperty("FirstName") public String getFirstName() { return firstName; } @JsonProperty("GovId") public String getGovId() { return govId; } }

次のコード例では、Person のインスタンスから IonStruct オブジェクトを作成します。

IonStruct ionStruct = (IonStruct) MAPPER.writeValueAsIonValue(new Person("Brent", "TOYENC486FH"));

Ion オブジェクトの作成

次のコード例では、ionStruct インスタンスの各フィールドを出力します。

// ionStruct is an instance of IonStruct System.out.println(ionStruct.get("GovId")); // prints TOYENC486FH System.out.println(ionStruct.get("FirstName")); // prints Brent

次のコード例では、IonStruct オブジェクトを読み取り、Person のインスタンスにマップします。

// ionStruct is an instance of IonStruct IonReader reader = IonReaderBuilder.standard().build(ionStruct); Person person = MAPPER.readValue(reader, Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH

Ion の操作方法の詳細については、GitHub で Amazon Ion のドキュメントを参照してください。QLDB で Ion を操作するコード例については、「Amazon QLDB で Amazon Ion のデータ型を操作する」を参照してください。