本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
适用于 Java 的 Amazon QLDB 驱动程序 — 食谱参考
重要
终止支持通知:现有客户将能够使用亚马逊,QLDB直到 2025 年 7 月 31 日终止支持。有关更多详细信息,请参阅将亚马逊QLDB账本迁移到亚马逊 Aurora Postgr SQL
本参考指南显示了 Java 版 Amazon QLDB 驱动程序的常见用例。它提供了 Java 代码示例,演示如何使用驱动程序运行基本的创建、读取、更新和删除 (CRUD) 操作。它还包括用于处理 Amazon Ion 数据的代码示例。此外,本指南还重点介绍了使事务具有幂等性和实现唯一性约束的最佳实践。
注意
在适用的情况下,某些用例对应的 Java QLDB 驱动程序的每个主要版本都有不同的代码示例。
目录
导入驱动程序
以下代码示例导入驱动程序、QLDB会话客户端、Amazon Ion 包和其他相关依赖项。
实例化驱动程序
以下代码示例创建了连接到指定分类账名称的驱动程序实例,并使用具有自定义重试限制的指定重试逻辑。
注意
此示例还实例化了 Amazon Ion 系统对象(IonSystem
)。在本参考中运行某些数据操作时,您需要此对象来处理 Ion 数据。要了解更多信息,请参阅使用 Amazon Ion。
CRUD操作
QLDB作为事务的一部分运行创建、读取、更新和删除 (CRUD) 操作。
警告
作为最佳实践,使写事务严格地幂等。
使事务幂等
我们建议将写事务设置为幂等,以避免重试时出现任何意想不到的副作用。如果事务可以运行多次并每次都产生相同的结果,则事务是幂等的。
例如,假设有一个事务,要将文档插入名为 Person
的表中。事务应该首先检查文档是否已经存在于表中。如果没有这种检查,表最终可能会有重复的文档。
假设在服务器端QLDB成功提交了事务,但是客户端在等待响应时超时。如果事务不是幂等的,则在重试的情况下可以多次插入相同的文档。
使用索引避免全表扫描
我们还建议您在索引字段或文档 ID 上使用相等运算符来运行带有WHERE
谓词子句的语句;例如,WHERE indexedField = 123
或 WHERE indexedField IN (456, 789)
。如果没有这种索引查找,则QLDB需要进行表扫描,这可能会导致事务超时或乐观的并发控制 () OCC 冲突。
有关 OCC 的更多信息,请参阅 亚马逊QLDB并发模型。
隐式创建的事务
QldbDriver.executExecutor
实例封装了隐式创建的事务。
您可以使用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.executeIonValue
参数(varargs)或单个List<IonValue>
参数。在 ion-javaIonList
被实现为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
实现唯一限制
QLDB不支持唯一索引,但你可以在应用程序中实现此行为。
假设您要对 Person
表中的 GovId
字段实现唯一性约束。据此,可以编写执行以下操作的事务:
-
断言该表中没有指定
GovId
的现有文档。 -
如果断言通过,请插入文档。
如果一个竞争事务同时通过断言,则只有一个事务将成功提交。另一个事务将因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 库
以下各节提供了使用这两种技术处理 Ion 数据的代码示例。
导入 Ion 软件包
将工件 ion-java
导入以下 Ion 软件包。
import com.amazon.ion.IonStruct; import com.amazon.ion.IonSystem; import com.amazon.ion.system.IonSystemBuilder;
将构件jackson-dataformat-ion2.10.0
或更高版本。
导入以下 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 文档