Java 的 Amazon QLDB 驅動程序-食譜參考 - Amazon Quantum Ledger 資料庫 (Amazon QLDB)

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

Java 的 Amazon QLDB 驅動程序-食譜參考

重要

支援結束通知:現有客戶將能夠使用 Amazon,QLDB直到 2025 年 7 月 31 日終止支援為止。有關更多詳細信息,請參閱將 Amazon QLDB 分類帳遷移到 Amazon Aurora 郵政. SQL

本參考指南顯示適用於 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 離子系統物件 (IonSystem)。在此參考中執行某些資料作業時,您需要此物件來處理 Ion 資料。如需進一步了解,請參閱與 Amazon 離子工作

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) 作業。

警告

作為最佳實踐,使您的寫入事務嚴格為冪等。

使交易冪等

我們建議您將寫入事務設為冪等,以避免在重試的情況下出現任何意外的副作用。如果事務可以運行多次並每次產生相同的結果,則事務是冪等的。

例如,假設將文件插入名為的資料表中的交易Person。事務應該首先檢查文檔是否已經存在於表中。如果沒有此檢查,表格最終可能會出現重複的文件。

假設成QLDB功地在服務器端提交事務,但客戶端在等待響應時超時。如果事務不是冪等的,則在重試的情況下,可以多次插入相同的文檔。

使用索引來避免完整表格掃描

我們也建議您在索引欄位或文件 ID 上使用相等運算子來執行具有述WHERE詞子句的陳述式;例如,WHERE indexedField = 123WHERE indexedField IN (456, 789)。如果沒有此索引查找,則QLDB需要進行表掃描,這可能會導致事務超時或樂觀的並發控制(OCC)衝突。

如需有關 OCC 的詳細資訊,請參閱 Amazon QLDB 并发模型

隱含建立的交易

QldbDriver.execute 方法接受一個 lambda 函數,該函數接收執行程的實例,您可以使用它來運行語句。執行個Executor體會封裝隱含建立的交易。

您可以使用方法在 lambda 函數中執行陳述Executor.execute式。當 lambda 函數返回時,驅動程序隱式地提交交易。

下列各節說明如何執行基本CRUD作業、指定自訂重試邏輯,以及實作唯一性條件約束。

注意

在適用的情況下,這些區段提供使用內建 Ion 程式庫和 Jackson Ion 映射程式庫來處理 Amazon Ion 資料的程式碼範例。如需進一步了解,請參閱與 Amazon 離子工作

建立資料表

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衝突例外狀況或交易逾時。

在一個語句中插入多個文檔

若要使用單一INSERT陳述式插入多個文件,您可以將類型的參數 IonList(明確轉換為 aIonValue) 傳遞至陳述式,如下所示。

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

傳遞的時候,您不會將變數預留位置 (?) 括在雙角括號 (<<...>>) 中IonList在手動 PartiQL 陳述式中,雙角括號表示稱為袋子的無序集合。

TransactionExecutor.execute 方法被重載。它接受可變數量的IonValue參數(可變參數)或單List<IonValue>個參數。在離子 Java 中,IonList被實現為一個. List<IonValue>

當您調用重載方法時,Java 默認為最具體的方法實現。在這種情況下,當您傳遞IonList參數時,它會預設為採用List<IonValue>. 調用時,此方法實現將列表中的IonValue元素作為不同的值傳遞。因此,要調用可變參數方法,您必須明確地將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 離子工作

有多種方法可以在中處理 Amazon 離子數據QLDB。您可以使用 Ion 庫中的內置方法,根據需要靈活地創建和修改文檔。或者,您可以使用更快XML的 Ion 傑克遜數據格式模塊將 Ion 文檔映射到普通的舊 Java 對象(POJO)模型。

以下各節提供使用這兩種技術處理離子資料的程式碼範例。

匯入離子套件

將工件離子 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;

初始化離子

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

建立離子物件

下列程式碼範例會使用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

假設您有一個名為的 JSON-map 模型類別Person,如下所示。

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; } }

下列程式碼範例會從的執行個體建立IonStruct物件Person

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

讀取離子物件

下列程式碼範例會列印執行個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 的詳細資訊,請參閱上的 Amazon Ion 文件 GitHub。如需中使用 Ion 的更多程式碼範例QLDB,請參閱使用 Amazon 離子數據類型在 Amazon QLDB