适用于 Java 的 Amazon QLDB 驱动程序 — 食谱参考 - 亚马逊 Quantum Ledger 数据库(亚马逊QLDB)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

适用于 Java 的 Amazon QLDB 驱动程序 — 食谱参考

重要

终止支持通知:现有客户将能够使用亚马逊,QLDB直到 2025 年 7 月 31 日终止支持。有关更多详细信息,请参阅将亚马逊QLDB账本迁移到亚马逊 Aurora Postgr SQL e。

本参考指南显示了 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) 操作。

警告

作为最佳实践,使写事务严格地幂等。

使事务幂等

我们建议将写事务设置为幂等,以避免重试时出现任何意想不到的副作用。如果事务可以运行多次并每次都产生相同的结果,则事务是幂等的

例如,假设有一个事务,要将文档插入名为 Person 的表中。事务应该首先检查文档是否已经存在于表中。如果没有这种检查,表最终可能会有重复的文档。

假设在服务器端QLDB成功提交了事务,但是客户端在等待响应时超时。如果事务不是幂等的,则在重试的情况下可以多次插入相同的文档。

使用索引避免全表扫描

我们还建议您在索引字段或文档 ID 上使用相等运算符来运行带有WHERE谓词子句的语句;例如,WHERE indexedField = 123WHERE indexedField IN (456, 789)。如果没有这种索引查找,则QLDB需要进行表扫描,这可能会导致事务超时或乐观的并发控制 () OCC 冲突

有关 OCC 的更多信息,请参阅 亚马逊QLDB并发模型

隐式创建的事务

QldbDriver.execut e 方法接受一个 lambda 函数,该函数接收一个 E xecutor 的实例,你可以用它来运行语句。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冲突异常或事务超时。

在一条语句内插入多个文档

要使用单个INSERT语句插入多个文档,可以向该语句传递一个类型的参数 IonList(显式转换为IonValue),如下所示。

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

传递 IonList 列表时,不要将变量占位符(?)包括在双尖括号( <<...>> )内。在手动 PartiQL 语句中,双尖括号表示名为bag的无序集合。

TransactionExecutor.execute 方法已过载。它接受可变数量的 IonValue参数(varargs)或单个List<IonValue>参数。在 ion-java 中,IonList 被实现为List<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

有多种方法可以处理中的 Amazon Ion 数据QLDB。您可根据需要使用 Ion 库 中的内置方法,灵活创建和修改文档。或者,你可以使用 Faster XML 的 J ackson 数据格式模块让 Ion 文档映射到普通的旧 Java 对象 (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

假设您有一个名为的JSON映射模型类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"));

读取 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 的更多信息,请参阅上的 Amazon Ion 文档 GitHub。有关在中使用 Ion 的更多代码示例QLDB,请参阅在亚马逊中使用 Amazon Ion 数据类型 QLDB