DynamoDB简介 (Introduction to DynamoDB)
AWS DynamoDB 是亚马逊提供的一种 NoSQL 数据库,适用于需要快速访问的大规模应用程序。NoSQL 数据库指的是非关系型数据库(或许应该称为“非关系数据库”)。关系型数据库是你之前可能使用过的熟悉的数据库,例如 MySQL 和 Microsoft SQL Server。这些数据库通过 SQL 查询进行访问,允许连接和其他多表查询。NoSQL 数据库不支持完整的 SQL 语言,尽管它们可能支持部分 SQL 语言。
NoSQL 数据库没有标准的访问机制。不同的云提供商有不同格式的 NoSQL 数据库,一个提供商的访问机制可能在另一个提供商的实现上不起作用。此外,数据也没有标准格式。DynamoDB 选择使用索引表和文档作为其数据格式。因此,你需要记住,其他类型的 NoSQL 数据库也在使用中,并且需要不同的访问技术,性能也会有所不同。
在本次实验课中,我们将看到 AWS 控制台如何为我们提供大多数创建和操作 DynamoDB 数据存储的功能。我们还将了解如何使用 Java SDK 进行访问,这使我们能够实现直接管理数据的应用程序,以及其他具有 Java API 允许访问的 AWS 服务。
在模块 2 中,我们已经学习了如何使用 AWS SDK 2.5 进行 S3 操作。SDK 的 2.x 版本仍然不支持一些高级库,包括 Amazon S3 传输管理器和 Amazon DynamoDB 映射器。在模块 3 中,我们将使用 AWS SDK v1.x,看看如何进行 DynamoDB 表的读取、更新和删除操作。我们将看到 DynamoDBMapper 类,它有助于在客户端类和 DynamoDB 表之间进行映射。可以在 pom.xml 中添加不同版本的 SDK。
创建 DynamoDB 数据库 (Creating DynamoDB Database)
我们将从创建一个 DynamoDB 表开始。我们将使用 AWS 管理控制台来创建表,尽管也可以通过编程方式完成。
当我们首次运行控制台并选择 DynamoDB 产品时,将会出现初始的 DynamoDB 屏幕。它看起来如下图所示。请注意,由于屏幕上有各种关于 DynamoDB 的信息,并且顶部有另一个亚马逊产品的广告,因此屏幕可能会略有不同。重要的是我们将要使用的被圈出的按钮。
在上图中可以看到,还有“入门指南”和“添加和查询项目”以及“监控和管理表”屏幕的链接。你可以现在查看指南,但我们需要至少创建一个表,然后才能探索其他选项。
通过点击上图中圈出的“创建表”按钮,我们可以创建一个新表。我们将创建一个具有一个字段(称为分区键)主键的非常简单的表。下图显示了表名和主键“UnitCode”。DynamoDB 有两种主键类型:一个单字段分区键,作为表索引,或一个分区键和排序键,允许多个字段作为表的唯一索引。
创建表屏幕还有一个复选框,表示使用“默认设置”下的“设置”窗格。每个表我们可以选择各种设置,但在本实验课程中我们将使用默认设置。
当我们点击“创建表”按钮时,AWS 将为我们创建表。这将需要一些时间,请耐心等待。创建表后,你的屏幕将如下图所示。
这是表级别的 DynamoDB 屏幕。注意左侧选项卡有几个可选择的选项。如果你想知道,“DAX” 是一个单独的亚马逊产品,构建内存缓存以加速数据访问。表“Units”由一个单选按钮选择。由于我们只创建了一个表,因此它是唯一选定的表。如果我们有多个表,则单选按钮将允许选择单个表。右侧显示了一组选项卡,允许我们更改单选按钮选择的表的属性和内容。在此阶段,它显示了表的概述。请注意,当我们返回此屏幕时,将不会选择任何表,因此右侧对话框将不可见。
我们现在可以向表中添加一些数据。记住,主键对于每个数据项(在关系数据库中相当于“行”)必须是唯一的。“操作”按钮显示一个下拉操作列表,允许对选定的项目进行复制、编辑和删除操作(尚未显示)。要添加一些数据,我们点击“操作”下拉列表,并选择“创建项目”选项。
此时,“创建项目”按钮允许我们向表中添加其他项目。由于尚无项目,因此我们现在可以使用的唯一按钮是创建新项目。
我们将输入单元代码“PROG2003”作为值。然而,我们还希望将单元名称字段添加到数据中。为此,我们输入代码,然后点击“添加新属性”按钮,显示如下所示的下拉列表。这列出了字段类型。如果我们选择“字符串”选项,屏幕将如下所示。
然后我们可以输入新数据。注意对话框要求输入字段名和字段值。这与关系数据库非常不同,在关系数据库中,表中的每个项目(行)都有相同的字段名称。在 DynamoDB 中,只有主键中的字段需要包含在每个项目中。这就是这个例子中的“UnitCode”字段。
如果我们输入“UnitName”作为字段名,并输入“Cloud Systems Development”作为数据,然后点击对话框底部的“保存更改”按钮,屏幕将更改为显示表中的数据如下所示。
我们将通过与添加第一个项目相同的方式向表中添加更多数据。以下截图显示了向表中添加的另外两个项目。
注意每个项目旁边有一个复选框。我们可以选择表中的一个或多个项目。这允许“操作”下拉列表对所有选定的项目执行操作。当这出现在你的屏幕上时,你还会注意到每个项目的主键成为超链接。如果你点击该键,你可以编辑项目中的数据。这与使用复选框选择项目,然后从“操作”按钮的下拉列表中选择“编辑”具有相同的效果。
活动 17(教程活动) (Activity 17)
创建 DynamoDB 表并添加一些项目。
按照上述步骤创建 Units 表并添加一些数据项。 选择一个项目并更改单元名称。 删除其中一个项目。 添加一个具有不同字段名的新项目。解释为什么会发生这种情况。 添加一个具有现有单元代码但不同名称的新项目。解释会发生什么。
复合主键 (Composite Primary Key)
在本节中,我们将创建一个具有复合主键的表。如前所述,这是一个具有多个属性作为访问表的主键的表。根据我们存储的数据,这提供了一种更灵活但仍然快速的数据结构。
我们现在将创建一个具有以下两个属性作为主键的表:
- UnitCode: String
- Session: String – 代表学习期间,例如“T1-2022”
此结构可用于存储每个学习期间的唯一数据,例如每个期间的注册人数。表的目的是存储每个期间的注册信息。为了使示例简单,我们只会存储一个进一步的数字属性来表示注册人数。
请注意,我们可以在前面的单元代码分区键的表中实现此表数据。但是,通过将会话作为复合键的一部分,我们可以确保在使用作为项目键一部分的会话进行搜索时实现快速访问。
要创建此表,我们按照上述 Units 表的步骤并点击“创建表”按钮。这次我们将通过勾选排序键复选框来添加排序键。点击“创建”按钮之前,屏幕将如下图所示。此屏幕截图中圈出的是复选框和第二个排序属性。
这里第二个排序字段是一个名为“Session”的字符串属性。该表将由 UnitCode 和 Session 字段索引。
我们可以像上面显示的那样向表中添加数据,但是这次每个表项必须提供单元代码和会话属性。以下截图显示了一些数据。请注意,要输入数据,我们必须使用前面表示例中显示的编辑屏幕。我们必须仔细拼写表属性名称,尽管我们可以稍后更正错误。
DynamoDB 表的一个推荐属性是尽可能减少表的数量。这具有性能优势。如果可能,建议每个事务只使用一个表。这意味着 DynamoDB 表中的数据经常基于索引的使用。排序字段(例如此处使用的 Session 属性)可用于标记不同的数据结构。以下截图显示了添加到表中的数据(表按单元代码排序)。
这种结构使数据相当分散,当然不像关系数据库那样干净,关系数据库会创建单独的表。但是,通过在应用程序中使用单一索引表,可以大大提高访问速度。
你可能还注意到表数据顶部的查询功能。这允许使用 AWS 控制台手动查询非常大的表(请记住,在 DynamoDB 中表没有大小限制)。一般来说,查询功能是自解释的。以下是一些示例。第一个示例是使用查询功能显示所有单元名称。
第二个示例基于使用会话和注册数据的过滤器。
如前所述,你可以使用每个项目旁边的复选框选择显示的项目,或者点击主键(这是一个超链接)来编辑项目中的数据。
活动 18(教程活动) (Activity 18)
向表中添加项目。
创建一个新的“Unit_Enrolments”表,如上所示。根据需要添加额外数据。如果出现错误,你可以随时通过点击项目中的主键或选择项目旁边的复选框进行编辑。 尝试更改其中一个表项的注册人数。 尝试上述两个查询。如果你添加了自己的数据,请确保显示的数据是正确的数据。 创建另一个名为“Cars”的表,模式如下:Cars(Id, Make, Model, Price, Year, Colour)。其中 Id 为主键。属性的数据类型为:Id、Price、Year - 数字,Make、Model、Colour - 字符串。 在“Cars”表中添加三个项目。
设置 DynamoDB 表和 Java 应用程序 (Setting-up DynamoDB Table and Java App)
我们已经看到如何从 AWS 管理控制台打开 DynamoDB,并如何使用可用的菜单创建带有主键和其他属性的表。请注意,DynamoDB 也可以本地访问,但我们使用它作为亚马逊的 Web 服务。我们将使用以下示例表“Students”,其模式如下:
Students(Id: Number, StudentName: String, UnitName: String)
我们将看到一些基本的数据库操作,包括读取、插入和删除。
我们将使用一个模板 Maven 应用程序来进行本次练习,就像我们在前几周所做的一样。我们一直使用以下命令来创建一个新的应用程序(例如 mydbappv2),使用 Maven 模板。主类位于 App.java 中,pom.xml 是项目对象模型文件。我们使用以下命令在 Cloud9 中创建一个 Maven 应用程序。
mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=au.edu.scu.app -DartifactId=ddbapp
现在我们需要更改 pom.xml 文件,附录 1 中提供的源代码是本周实验室的工作文件。该 pom.xml 包含 SDK 1.x 和 2.x 的依赖项。然而,也可以使用 S3 上实验室 3 文件的附录 1(在这种情况下,添加以下依赖项)。SDK 1.x 所需的新依赖项如下所示。我们使用的是 AWS SDK for Java 1.11.119,与 SDK 2.x 稍有不同。更多详情请参见:使用 SDK for Java 1.x 和 2.x
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
<version>1.11.119</version>
</dependency>
我们可以将此依赖项添加到上周的 pom.xml 文件中。
你还会看到另一个依赖项:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
这个依赖项是用于 SLF4J(简单日志门面)的简单实现。SLF4J 作为各种日志框架(如 Log4j、JDK 日志等)的简单门面或抽象,允许开发人员在不显著改变应用程序代码的情况下在实现之间切换。slf4j-simple 工件为 SLF4J 提供了一个基本的日志实现。它通常在开发过程中使用,或者在不需要高级日志功能或性能优化的小型应用程序中使用。在基于 Maven 的 Java 应用程序中,添加此依赖项允许你使用 SLF4J 进行日志记录,并且配置简单,设置最少。指定的版本(1.7.21)表示你的应用程序将使用的 SLF4J API 版本,确保日志行为的一致性和兼容性。
从 DynamoDB 表中检索项目 (Retrieving an Item from a DynamoDB Table)
本节展示如何读取 DynamoDB 表中的项目。首先需要导入以下类:
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.model.AmazonDynamoDBException;
由于我们通过 AWS 管理控制台在 Cloud9 中使用 DynamoDB,因此不需要设置 AWS 凭证,如果在本地使用 DynamoDB 则需要。我们将按照以下步骤从“Students”表中检索单个项目:
- 实例化“AmazonDynamoDB”类,指定 AWS 区域和端点。AWS Educate 仅在 N. Virginia 区域免费,因此我们使用 US_EAST_1。
- 使用“AmazonDynamoDB”对象创建“DynamoDB”类的实例。
- 创建“Table”类的实例以表示“Students”表。
- 调用“Table”实例的“getItem()”方法。必须指定要检索的项目的主键“Id”。添加了一个带有“AmazonDynamoDBException”的简单 try-catch 语句。
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion(Regions.US_EAST_1).build();
DynamoDB dynamoDB = new DynamoDB(client);
try {
Table table = dynamoDB.getTable("Students");
Item item = table.getItem("Id", 100);
System.out.println(item.toJSONPretty());
}
catch (AmazonDynamoDBException e) {
System.out.println(e.getErrorMessage());
}
要读取从 getItem() 方法返回的值,需要使用 toJSONPretty()。上例的输出如下:
{
"UnitName" : "Cloud Systems Development",
"Id" : 100,
"StudentName" : "Smith"
}
如果我们不想检索整个项目(行),而只想检索表的某些特定属性,可以使用“GetItemSpec”类。我们仍然需要指定主键。“ProjectExpression”是一个字符串,用于指定所需的表属性。在使用“ProjectExpression”时,需要注意当列名与 DynamoDB 保留字相同时会抛出异常。完整列表请参见这里。
import com.amazonaws.services.dynamodbv2.document.spec.GetItemSpec;
GetItemSpec spec = new GetItemSpec()
.withPrimaryKey("Id", 100)
.withProjectionExpression("Id, StudentName")
.withConsistentRead(true);
Item item = table.getItem(spec);
System.out.println(item.toJSONPretty());
输出如下:
{
"Id" : 100,
"StudentName" : "Smith"
}
可以使用 getItem() 和 getString() 方法访问单个属性值,如下所示:
System.out.println(item.getInt("Id"));
读取多个项目 (Reading Multiple Items)
我们可以使用 scan 方法从 DynamoDB 表中读取多个项目。此方法根据称为过滤表达式的可选规范读取每个项目(类似于投影表达式)。需要导入以下类:
import com.amazonaws.services.dynamodbv2.document.ScanOutcome;
import com.amazonaws.services.dynamodbv2.document.spec.ScanSpec;
import com.amazonaws.services.dynamodbv2.document.ItemCollection;
import java.util.Iterator;
import java.util.HashMap;
以下程序读取 Id 属性值在 100 和 102 之间的所有项目,并显示 Id、StudentName 和 UnitName 属性值。
HashMap<String, String> nameMap = new HashMap<String, String>();
nameMap.put("#id", "Id");
HashMap<String, Object> valueMap = new HashMap<String, Object>();
valueMap.put(":start_id", 100);
valueMap.put(":end_id", 102);
ScanSpec scanSpec = new ScanSpec().withProjectionExpression("#id, StudentName, UnitName")
.withFilterExpression("#id between :start_id and :end_id")
.withNameMap(nameMap)
.withValueMap(valueMap);
try {
ItemCollection<ScanOutcome> items = table.scan(scanSpec);
Iterator<Item> iter = items.iterator();
while (iter.hasNext()) {
Item item = iter.next();
System.out.println(item.toString());
}
}
catch (AmazonDynamoDBException e) {
System.out.println(e.getErrorMessage());
}
两个 HashMap 实例用于表示名称映射(主键属性名)和值映射(主键值的范围)。使用投影表达式(告知需要哪些列值)和过滤表达式(告知主键值的范围)创建 ScanSpec 类的实例。然后完成名称映射和值映射以完成查询。
我们可以调用 scan() 方法运行查询。它返回一组可以存储到 ItemCollection 实例中的项目。ItemCollection 实例可以使用 iterator() 方法进行迭代。我们可以在这个迭代器实例上运行一个循环来读取每个项目。输出如下:
{ Item: {UnitName=Cloud Systems Development, Id=100, StudentName=Smith} }
{ Item: {UnitName=Web Development I, Id=102, StudentName=Sarah} }
{ Item: {UnitName=Programming II, Id=101, StudentName=Helen} }
活动 19(教程活动) (Activity 19)
使用循环读取表项。
要读取项目,我们需要指定主键的值。因此,你需要定义一个包含“Id”属性值的数组。然后你可以使用此数组将值传递给“getItem()”方法。
读取“Cars”表的所有当前项目,包括其所有属性值。 仅显示“Cars”表的“Make”、“Model”和“Price”属性值。使用投影表达式实现此目的。
活动 20(教程活动) (Activity 20)
读取特定项目。
使用上一节中显示的 scan 方法实现此 SQL。 "select * from Cars where Id is greater than 0."
向 DynamoDB 表插入项目 (Inserting an Item into DynamoDB Table)
插入项目的步骤与我们刚刚看到的读取项目的步骤类似。在这种情况下,Item 类对象需要包括主键以及所有其他属性的值。首先需要导入以下内容:
import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
这些步骤将向“Students”表中插入一个项目。
- 使用 AmazonDynamoDB 类的对象创建 DynamoDB 类的实例。
- 创建 Table 类的实例以表示“Students”表。
- 创建 Item 类的实例以表示新项目。必须为 Item 对象指定所有属性及其主键。
- 使用步骤 3 中创建的 Item 对象调用 Table 对象的 putItem() 方法。
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion(Regions.US_EAST_1).build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("Students");
try {
Item item = new Item().withPrimaryKey("Id", 104)
.withString("StudentName", "Harry")
.withString("UnitName", "Cloud Systems Development");
PutItemOutcome outcome = table.putItem(item);
}
catch (AmazonDynamoDBException e) {
System.out.println(e.getErrorMessage());
}
从 DynamoDB 表中删除项目 (Deleting Items from DynamoDB Table)
步骤 1 和 2 与删除项目相同。在步骤 3 中,Item 对象需要指定我们要删除的值的主键。首先使用以下导入:
import com.amazonaws.services.dynamodbv2.document.DeleteItemOutcome;
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion(Regions.US_EAST_1).build();
DynamoDB dynamoDB = new DynamoDB(client);
try{
Table table = dynamoDB.getTable("Students");
DeleteItemOutcome outcome = table.deleteItem("Id", 101);
}
catch(AmazonDynamoDBException e) {
System.out.println(e.getErrorMessage());
}
上述操作将删除属性“Id”为“101”的项目。
在 DynamoDB 表中更新项目 (Updating an Item in DynamoDB Table)
首先应添加以下类:
import com.amazonaws.services.dynamodbv2.document.UpdateItemOutcome;
import com.amazonaws.services.dynamodbv2.document.AttributeUpdate;
import com.amazonaws.services.dynamodbv2.document.PrimaryKey;
以下步骤将更新主键为“104”的项目中的“StudentName”列的值为“Harry”。
- 使用 AmazonDynamoDB 类的对象创建 DynamoDB 类的实例。
- 创建 Table 类的实例以表示“Students”表。
- 创建 PrimaryKey 类的实例,以指定要更新的值“104”。
- 创建 AttributeUpdate 类的实例,以指定要更新的列/属性及其更新的值。
- 使用 PrimaryKey 和 AttributeUpdate 对象调用 Table 对象的 updateItem() 方法。
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion(Regions.US_EAST_1).build();
DynamoDB dynamoDB = new DynamoDB(client);
try{
Table table = dynamoDB.getTable("Students");
PrimaryKey primaryKey = new PrimaryKey("Id",104);
AttributeUpdate attrUpdate = new AttributeUpdate("StudentName").put("Darwin");
UpdateItemOutcome outcome = table.updateItem(primaryKey, attrUpdate);
}
catch(AmazonDynamoDBException e) {
System.out.println(e.getErrorMessage());
}
活动 21(教程活动) (Activity 21)
从“Cars”表中插入、更新和删除。实现以下功能。
使用循环向表中插入三个新项目。 将最后插入的项目更新为 Make="Volkswagen"。 从表中删除一个项目(任何项目)。
使用 DynamoDBMapper 插入 (Inserting using DynamoDBMapper)
我们将使用 DynamoDBMapper 类将本地类“StudentsItem”映射到 DynamoDB 表中。映射器类如下所示:
package au.edu.scu.app;
import java.util.Set;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
@DynamoDBTable(tableName="Students")
public class StudentsItem {
private Integer id;
private String studentName;
private String unitName;
@DynamoDBHashKey(attributeName="Id")
public Integer getId() { return id; }
public void setId(Integer id) {this.id = id; }
@DynamoDBAttribute(attributeName="StudentName")
public String getStudentName() {return studentName; }
public void setStudentName(String studentName) { this.studentName = studentName; }
@DynamoDBAttribute(attributeName="UnitName")
public String getUnitName() { return unitName; }
public void setUnitName(String unitName) { this.unitName = unitName; }
}
这个类实际上定义了一个名为“StudentsItem”的普通 Java 对象(POJO),它使用注释将对象字段映射到“Students”表的属性名。以下代码将向“Student”表中插入一个项目。使用“AmazonDynamoDB”实例创建一个“DynamoDBMapper”对象。创建“StudentsItem”类的一个实例,将要插入的值映射到各个属性(列)。最后,“DynamoDBMapper”类的“save()”方法将数据插入表中。可以使用以下包添加“DynamoDBMapper”类。
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion(Regions.US_EAST_1).build();
DynamoDBMapper mapper = new DynamoDBMapper(client);
StudentsItem item = new StudentsItem();
item.setId(110);
item.setStudentName("Charlene");
item.setUnitName("Programming I");
mapper.save(item);
DynamoDBMapper 不能用于在 DynamoDB 表中进行创建、删除、更新操作。