使用 AWS DynamoDB 低级 Java API – Spring Boot REST 应用程序





0/5 (0投票)
如何使用 AWS Java 2 API 创建一个使用 Spring Boot 的 REST 应用程序,该应用程序可以读写 DynamoDB 数据库
引言
在本教程中,我们使用 Amazon Web Services Java 2 应用程序编程接口 (API) 创建一个使用 Spring Boot 的 Rest 应用程序,该应用程序可读写 DynamoDB
数据库。本教程假定读者熟悉 AWS、具有 Java 编程经验和 Spring Boot 经验。但是,即使没有这些经验,本教程也应该很有用,因为它提供了大量的补充资源供您查阅。
在这里,我们创建一个简单的数据库,其中包含通过摄像头收集的“观测站”和“观测数据”。不管怎样……请暂时抛开怀疑,继续阅读。现在,假设这些站点需要一种将观测数据上传到关联的 DynamoDB
表格的方法。我们决定使用 Rest API 让站点上传数据。我们使用 Spring Boot Rest 应用程序来实现这个 API。同样,如果这一切听起来可疑,请暂时抛开怀疑,专注于 AWS 代码而非应用程序设计。
在本教程中,我们将:
- 使用
DynamoDB
控制台创建两个数据库表 - 使用控制台创建几个项目
- 创建一个 IAM 编程用户
- 创建一个提供 Rest 端点的 Spring Boot 应用程序,以便客户端应用程序可以:
- 写入观测数据
- 读取观测数据
- 更新观测数据
- 删除观测数据
- 批量写入多个观测数据
- 有条件地查询站点的观测数据
- 并有条件地更新观测数据
- 并使用 Postman 测试 Rest 端点
本教程的目的是探索 DynamoDB
,而不是介绍 Spring Boot、Rest 或 JSON,并假设读者对这三者都有基本了解。但是,如果对其中任何一个主题不熟悉,则提供了链接供您在继续之前学习。
NoSQL 数据库
DynamoDB
是一个键值和文档 NoSQL 数据库。如果您不熟悉 NoSQL 文档数据库,应在继续之前熟悉它。以下是介绍 NoSQL 数据库的入门视频。
以下是两篇写得很好的关于 NoSQL 和 DynamoDB
的入门文章。
请注意,Amazon 还提供了 DocumentDB,我们可以将其用作 DynamoDB
的替代方案。但是,DocumentDB
将在不同的教程中介绍。
从最根本的层面来看,DynamoDB
数据库可以描述如下。表由项目组成。一个项目有一个或多个属性。在一个表中,您定义分区键,并可选地定义排序键。分区键是一个键值对,它不仅唯一标识一个项目,还决定了该项目在计算机存储上的分布方式。排序键不仅对项目进行逻辑排序,还相应地存储这些项目。显然,NoSQL 物理存储及其如何实现可扩展性还有更多内容,但这超出了本教程的范围。
Amazon Web Services 和 DynamoDB
Amazon DynamoDB
是一种 NoSQL 键值和文档数据库,作为云服务提供。它完全托管,允许用户避免与托管企业数据库相关的管理任务。与 Amazon 的几乎所有产品一样,它都可以通过 Rest API 进行访问。
Amazon 提供了软件开发工具包 (SDK) 来简化 Rest API 的使用。支持的语言有 Java、C++、C#、Ruby、Python、JavaScript、NodeJs、PHP、Objective-C 和 Go。在本文中,我们使用 Java API。目前 API 有两个版本,在本教程中,我们使用 Java 2 SDK。
Java 2 AWS SDK 是 Java 1.1 AWS SDK 的重写,它从更传统的编程范式(使用构造函数实例化对象,然后使用 setter 设置属性)转变为流式接口/构建器编程风格。
流式接口
流式接口是 Martin Fowler 和 Eric Evans 创造的一个术语。它指的是一种编程风格,其中 public
方法(API)可以链式调用以执行任务。AWS Java SDK 2.0 在使用构建器时就采用了这种风格。构建器任务执行任务,但随后返回构建器的一个实例。这允许将方法链式调用。有关流式接口和构建器的更多信息,请参阅这篇博客文章:Java 的另一种构建器模式。
DynamoDB 低级 API
与所有 AWS API 一样,DynamoDB
通过 Rest 端点公开。AWS SDK 提供了一个抽象层,使您无需直接调用 Rest。在该层之上,Java SDK 提供了一个名为 DynamoDBMapper
的类,它允许以类似于 Java Persistence Framework (JPA) 的方式使用 DynamoDB
。尽管有用,但使用低级 API 并不难。此外,在许多情况下,您不希望在对象模型中创建依赖于 DynamoDB
的依赖项。
例如,假设我们实现了一个将小部件存储在 DynamoDB
中的系统。如果使用 DynamoDBMapper
,则 Widget
模型类将通过将该类映射到 Widgets
表格的注解而依赖于 DynamoDB
。
或者,如果我们不想使用 DynamoDBMapper
,我们可以实现类似于以下图表的内容。它是一个典型的 DAO 模式,其中对 AWS SDK 的唯一直接依赖是 WidgetDaoImpl
类。有关 DAO 设计模式的更多信息,请参阅以下入门文章:DAO 设计模式。
在本教程中,我们使用 SDK 直接调用底层的 DynamoDB
Rest 端点。另外,请注意,我们不使用 DAO 设计模式,而是为了简洁起见,将数据访问逻辑直接放入控制器类中。但是,我们确实使用了基于 Rest 的 Spring MVC 设计模式。
- 有关
DynamoDB
低级 API 的更多详细信息,请参阅 Amazon DynamoDB 开发人员指南 中的 DynamoDB 低级 API 文档。 - 有关使用 Rest 的 Spring MVC 的更多信息,请参阅 Spring 控制器 – Spring MVC 控制器。
- 注意:如果您愿意让您的应用程序依赖于
DynamoDBMapper
,您应该考虑使用它,因为它大大简化了与DynamoDB
的交互。 - Java: DynamoDBMapper 文档
教程用例 – 站点观测数据
想象一下,我们有负责拍摄照片观测的站点。一个站点有一个坐标、一个地址和一个名称。一个站点有一个坐标。一个站点有一个地址。一个站点可以有无限的观测数据。
尽管本教程不讨论 NoSQL 数据库设计,但从下图中可以看出,我们需要两个表,Station
和 Observation
,这是合理的。此外,由于 Observation
表的写入强度非常高——站点将持续向应用程序发送观测数据——因此,不将观测数据作为 Station
实例中的集合包含,而是将其作为一个单独的表保留,这是有意义的。请记住,这些是 JSON 文档,而不是关系表。将 Observation
s 设计为 Station
内的项目列表是不合理的,并且会导致数据库过大和难以管理。
如果有足够的 Station
,为了更高的效率,我们可能会为每个 station
的 observation
s 创建一个单独的表。这将允许更大的吞吐量,用于写入和读取观测数据。但是,在本教程中,我们只是定义一个 stationid
来识别 observation
的 station
,并将在该值上创建一个索引。
DynamoDB 控制台
AWS 管理控制台提供了一种简单易用的基于 Web 的方式来使用 Amazon 的云服务。虽然本教程未涵盖,但对于不熟悉 AWS 的人来说,这里有一个 Amazon 解释管理控制台的短视频。请注意,AWS 还提供命令行界面和应用程序编程接口 (API) 来访问其云服务。
- AWS 基础知识:LinuxAcademy 提供的 AWS 控制台导航方法。
在开始本教程的编程部分之前,我们必须创建 DynamoDB
数据库。
创建站点表
- 进入 AWS 管理控制台后,导航到
DynamoDB
控制台。 - 点击 创建表 按钮。
- 提供
Station
作为表的名称,id
作为表的主键。
创建站点项目
请记住,DynamoDB
是无模式的。我们创建一个项目,但不定义表的模式。相反,我们创建几个具有所需结构的项目。
- 点击 项目 选项卡并点击 创建项目 按钮。
- 创建
id
和name
属性,将id
指定为Number
数据类型,将name
指定为 String。分别赋值为221
和 “Potomac Falls
”。 - 创建一个名为
address
的属性,并将其指定为Map
数据类型。 - 在
address
映射中添加city
、street
和zip
属性作为String
数据类型。在下面的示例中,我将Potomac
、230 Falls Street
和22333
分配为属性值。 - 创建一个
coordinate
作为Map
,并为其分配latitude
和longitude
属性作为Number
数据类型。我将38.993465
和-77.249247
分别赋值给latitude
和longitude
。 - 再重复一个站点。
我们在 Station
表中创建了两个项目。以下是这两个项目的 JSON 格式。
{
"address": {
"city": "Potomac",
"street": "230 Falls Street",
"zip": "22333"
},
"coordinate": {
"latitude": 38.993465,
"longitude": -77.249247
},
"id": 221,
"name": "Potomac Falls"
}
{
"address": {
"city": "Frederick",
"street": "9871 River Street",
"zip": "221704"
},
"coordinate": {
"latitude": 39.23576,
"longitude": -77.4545
},
"id": 234,
"name": "Monocacy River"
}
您可以通过选择项目,然后选择弹出窗口中的文本视图来查看创建项目后的 JSON。
请注意,前面的 JSON 文档是通用 JSON。实际的 JSON,即 DynamoDB
存储的 JSON(包括数据类型),如下所示。其中 M、S、N、SS 等代表元素数据类型。
{
"address": {
"M": {
"city": {
"S": "Frederick"
},
"street": {
"S": "9871 River Street"
},
"zip": {
"S": "221704"
}
}
},
"coordinate": {
"M": {
"latitude": {
"N": "39.23576"
},
"longitude": {
"N": "-77.4545"
}
}
},
"id": {
"N": "234"
},
"name": {
"S": "Monocacy River"
}
}
DynamoDB
数据类型为:
字符串
=S
数字
=N
布尔值
=BOOL
二进制
=B
日期
=S (存储为字符串)
字符串集
=SS
数字集
=NS
二进制集
=BS
映射
=M
和列表
=L
例如,在下面的 JSON 文档中,一个观测的 address
和 coordinate
都是 Map
数据类型,city
、street
、zip
是 String
数据类型,而 latitude
和 longitude
是 Number
数据类型。
您可以在弹出窗口中切换 JSON 和 DynamoDB
JSON,如下所示(请注意 DynamoDB
JSON checkbox
)。
创建观测表
创建 Station
表后,我们需要创建 Observation
表。
- 创建一个名为
Observation
的新表。 - 为其分配分区键
id
和排序键stationid
。
复合键(分区键和排序键)
分区键是表的主键,由单个属性组成。DynamoDB
使用此键创建哈希,以确定项目的存储方式。单独使用时,分区键唯一标识一个项目,因为没有两个项目可以具有相同的分区键。但是,当还定义了排序键时,一个或多个项目可以具有相同的分区键,前提是分区键与排序键的组合是唯一的。可以将其视为复合键。
排序键有助于 DynamoDB
更有效地存储项目,因为它将具有相同排序键的项目分组在一起(因此得名排序键,因为它使用此键对项目进行排序)。
一个观测数据应该有一个标识它的 id
,并且观测数据应该按站点排序,所以我们将 stationid
定义为表的排序键。
创建示例观测数据
与 Station
表一样,创建一些 Observation
项目而不是定义模式。
- 找到三个小尺寸的图像用于本项目。如果您愿意,可以使用本教程 Git 项目中的三个示例图像。
- 导航到 Code Beautify 的 将图像转换为 Base64 网页,并将这三张图像转换为
Base64 字符串
。 - 创建观测数据的 JSON 列表。
- 或者,如果您愿意,只需使用本教程 Git 项目中提供的 JSON sampleData.json 文件。
以下是包含四个观测数据的 JSON 列表。图像 base64 字符串
被截断,以便在此处轻松显示。您可以从本教程的 Git 项目中获取名为 observations.json 的原始文件。
{
[
{
"stationid": 221,
"date": "1992-03-12",
"time": "091312",
"image": "/9j/4AAQSkZJRgABAQAAYABg <snip> kf/9k="
},
{
"stationid": 221,
"date": "1999-09-22",
"time": "071244",
"image": "/9j/4AAQSkZJ <snip> D9KhoA//2Q=="
},
{
"stationid": 234,
"date": "2001-11-09",
"time": "111322",
"image": "/9j/4AAQSkZ <snip> WoGf/9k="
},
{
"stationid": 234,
"date": "2013-01-12",
"time": "081232",
"image": "/9j/4AAQS <snip> q5//2Q=="
}
]
}
Base64 编码
图像是二进制的。但是,所有二进制数据都可以通过正确编码和解码的 String
来表示。Base64
是一种编码方案,可将二进制数据转换为 string
。它很有用,因为它允许在文本文件(例如网页或 JSON 文档)中嵌入二进制数据(例如图像)。DynamoDB
在传输数据时使用 Base64
格式将二进制数据编码为 string
,并在存储数据时将字符串解码为二进制数据。因此,发送到我们创建的 Rest 端点的图像应进行 base64
编码。
创建 IAM 应用程序用户
在开始 Spring Boot 应用程序之前,我们需要一个具有对 AWS DynamoDB
API 编程访问权限的用户。如果您不熟悉 IAM,以下入门视频应该会有所帮助。否则,我们来创建一个用户。
- 导航到 IAM 控制台并单击“添加用户”。
- 创建一个名为
DynamoDBUser
的新用户。 - 为
DynamoDBUser
分配 编程访问权限。 - 创建一个名为
dynamo_users
的新组,并授予AmazonDynamoDBFullAccess
权限。 - 将
DynamoDBUser
分配给dynamo_users
组。 - 如果您正确创建了用户,您应该会看到以下 摘要 屏幕。
- 将凭证文件 credentials.csv 保存到本地硬盘。
Spring Boot 应用程序
现在我们已经创建了两个所需的表并创建了一个用户,我们可以开始示例应用程序了。我们创建一个 Rest API,供站点保存、检索、更新和删除观测数据。Spring Boot 的解释不多,所以如果您从未创建过 Spring Boot Rest 应用程序,您可能需要考虑完成一两个关于 Spring Boot 和 Rest 的教程。以下是两个教程的链接;但是,网上还有很多。
设置项目
- 创建一个新项目,我使用 Eclipse 创建了一个新的 Maven 应用程序。
- 修改 Maven POM 文件以匹配以下 POM。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tutorial.aws</groupId> <artifactId>DynamoDbTutorial</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.5.25</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <scope>test</scope> </dependency> <dependency> <artifactId>auth</artifactId> <groupId>software.amazon.awssdk</groupId> </dependency> <dependency> <artifactId>aws-core</artifactId> <groupId>software.amazon.awssdk</groupId> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>auth</artifactId> </dependency> <dependency> <artifactId>dynamodb</artifactId> <groupId>software.amazon.awssdk</groupId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>jar</goal> </goals> <configuration> <classifier>client</classifier> <includes> <include>**/factory/*</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
在 POM 中,我们定义了 AWS 物料清单 (BOM) 和所需的 AWS 库。请注意,当使用 BOM 时,无需指定库版本,因为 BOM 管理版本。我们还定义了所需的 Spring Boot 库。
- 在 resources 文件夹中创建一个 application.properties 文件。打开 credentials.csv 并将凭据添加到文件中,属性名称如下。
- 注意:此用户在发布本教程之前已删除。
cloud.aws.credentials.accessKey=AK <snip> WP cloud.aws.credentials.secretKey=yLJJ <snip> asUR cloud.aws.region.static=us-east-1
创建 Spring Boot 应用程序类
- 在
com.tutorial.aws.dynamodb.application
包中创建一个名为SiteMonitorApplication
的新类。 - 使用
@SpringBootApplication
注解该类。 - 创建
main
方法并让它启动 Spring Boot 应用程序。package com.tutorial.aws.dynamodb.application; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan({ "com.tutorial.aws.dynamodb" }) public class SiteMonitorApplication { public static void main(String[] args) { SpringApplication.run(SiteMonitorApplication.class, args); } }
创建观测数据对象
- 在
com.tutorial.aws.dynamodb.model
包中创建一个名为Observation
的类。 - 创建与上面创建的 JSON 数据中具有相同名称和类型的变量。
package com.tutorial.aws.dynamodb.model; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; public class Observation { private long stationid; private String date; private String time; private String image; private List<String> tags; public long getStationid() { return stationid; } public void setStationid(long stationid) { this.stationid = stationid; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public void setTags(List<String> tags) { this.tags = tags; } public List<String> getTags() { return this.tags; } @Override public String toString() { try { ObjectMapper mapper = new ObjectMapper(); return mapper.writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return null; } } }
Observation
对象的属性与 JSONObservation
文档中的属性相同。请注意,在toString
方法中,我们使用了Jackson
库中的ObjectMapper
。我们没有在 POM 中包含此库,因为spring-boot-starter-web
库包含此库。ObjectMapper
将 JSON 映射到对象,并将对象映射到 JSON。这是 Spring Rest 完成此任务的方式。在toString
方法中,我们告诉ObjectMapper
实例将Observation
对象写入为 JSONstring
。有关ObjectMapper
的更多信息,这里有一个更深入地解释该类的教程:Jackson ObjectMapper。
创建 Rest 控制器
Rest 控制器提供外部 API,供站点向我们的应用程序发送数据。通过 API,客户端应用程序将数据传输到 DynamoDB
数据库。不同的站点可以使用任何支持 Rest 的语言开发自己的客户端应用程序。唯一的要求是站点的数据遵循预期的 JSON 格式。
- 注意:我们通过将数据访问直接放入控制器来违反 MVC 设计模式。请暂时忽略这种反模式。
让我们创建一个 Rest 控制器来定义我们应用程序的 API。
- 在
com.tutorial.aws.dynamodb.api
包中创建一个名为ObservationApiController
的类,并使用@RestController
注解对其进行标注。 - 将其顶级路径指定为
/observations
。 - 创建一个用于上传新
Observation
的 Rest 端点。为其分配/observation
映射,并将方法命名为createObservation
。 - 让该方法将
Observation
作为请求的正文。 - 让该方法将上传的
Observation
打印到命令行。package com.tutorial.aws.dynamodb.api; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tutorial.aws.dynamodb.model.Observation; @RestController @RequestMapping(value = "/observations") public class ObservationApiController { @PostMapping("/observation") public void createObservation(@RequestBody Observation observation) { System.out.println(observation.toString()); } }
- 使用 Maven 编译应用程序并启动应用程序。
- 应用程序启动后,我们可以使用 Postman 进行测试。
使用 Postman 进行测试
Postman 是一个用于测试 JSON 端点的有用工具。如果您从未使用过 Postman,您可能需要先完成一两个教程。
- Postman 学习中心:入门。
- 创建一个名为
AddObservation
的新请求,该请求执行 Rest 端点。https://:8080/observations/observation
- 将之前创建的 JSON 文档中的一个观测数据放入请求的
Body
中。将类型指定为 JSON (application/json
)。Postman 中用于保存观测数据的 JSON 请求。 - 点击 发送 将请求发送到 Spring Rest 端点。如果一切正确,您应该会看到
Observation
以 JSON 格式打印到命令行。{"stationid":221,"date":"1992-03-12", "time":"091312", "image":"/9j/4AAQSkZJRgAB <snip> Wxkf/9k=","tags":null}
- 复制图像
base64 字符串
并导航到 CodeBeautify 网站的 将您的 Base64 转换为图像 网页。将string
粘贴到提供的textarea
中,然后点击 生成图像。如果base64 字符串
发送正确,您应该会看到发送到 Rest 端点的相同图像。
创建 DynamoDB 客户端
现在我们已经有了基本的 Spring Boot 应用程序,我们可以开始构建实际的 DynamoDB
API 了。但在使用 DynamoDB
之前,我们需要创建一个 DynamoDBClient
实例。
- 在
com.tutorial.aws.dynamodb.service
包中创建一个名为ObservationService
的类。 - 添加 spring 的
@Service
注解,以便 spring 将此类视为控制器。 - 添加
key
和secretKey
参数,并使用@Value
注解表示它们是应用程序application.properties
文件中的参数(Spring Framework 文档)。 - 创建
@PostConstruct
和@PreDestroy
方法(或实现一个 Spring<a href="https://docs.springframework.org.cn/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/InitializingBean.html" rel="noreferrer noopener">InitializingBean</a>
)。 - 创建一个名为
dynamoDbClient
的成员变量,类型为DynamoDbClient
。 - 在
initialize
方法中实例化并加载dynamoDbClient
的凭据。 - 在
preDestroy
方法中关闭dynamoDbClient
。package com.tutorial.aws.dynamodb.service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.tutorial.aws.dynamodb.model.Observation; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; @Service public class ObservationService { @Value("${cloud.aws.credentials.accessKey}") private String key; @Value("${cloud.aws.credentials.secretKey}") private String secretKey; private DynamoDbClient dynamoDbClient; @PostConstruct public void initialize() { AwsBasicCredentials awsCreds = AwsBasicCredentials.create(key, secretKey); DynamoDbClient client = DynamoDbClient.builder() .credentialsProvider(StaticCredentialsProvider.create(awsCreds)) .region(Region.US_EAST_1).build(); this.dynamoDbClient = client; } @PreDestroy public void preDestroy() { this.dynamoDbClient.close(); } }
DynamoDBClient
DynamoDBClient
提供对 DynamoDB
API 的访问。所有与 DynamoDB
的交互都通过此类完成。它具有用于读取、写入、更新以及与 DynamoDB
表和项目进行其他交互的方法。有关更多信息,请参阅 API 文档。
写入观测数据
我们首先将一个 Observation
写入 DynamoDB
。或者,您可以说我们将一个项目 PUT 到 DynamoB
,因为我们正在向 DynamoDB
发出 HTTP Put
请求。我们通过使用 DynamoDBClient
的 putItem
方法结合 PutItemRequest
来完成此操作。
修改服务类
- 创建一个名为
writeObservation
的方法,该方法将Observation
作为参数。 - 创建一个
HashMap
,使用String
作为键,AttributeValue
作为值。 - 将每个
Observation
变量放入HashMap
中,确保键名正确。键名应与 JSON 完全相同。 - 为每个变量创建
AttributeValueBuilder
时,请确保使用正确的数据类型方法。 - 构建一个新的
PutItemRequest
,然后让dynamoDbClient
调用其putItem
方法,将观测数据写入Observation
DynamoDB
表。package com.tutorial.aws.dynamodb.service; import java.util.HashMap; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.tutorial.aws.dynamodb.model.Observation; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; <snip> public void writeObservation(Observation observation) { HashMap<String, AttributeValue> observationMap = new HashMap<String, AttributeValue>(); observationMap.put("id", AttributeValue.builder() .s(observation.getStationid() + observation.getDate() + observation.getTime()).build()); observationMap.put("stationid", AttributeValue.builder() .n(Long.toString(observation.getStationid())).build()); observationMap.put("date", AttributeValue.builder() .s(observation.getDate()).build()); observationMap.put("time", AttributeValue.builder() .s(observation.getTime()).build()); observationMap.put("image", AttributeValue.builder() .b(SdkBytes.fromUtf8String(observation.getImage())).build()); if (observation.getTags() != null) { observationMap.put("tags", AttributeValue.builder() .ss(observation.getTags()).build()); } PutItemRequest request = PutItemRequest.builder() .tableName("Observation").item(observationMap).build(); this.dynamoDbClient.putItem(request); } }
AttributeValue
DynamoDB
Java API 中有四种不同的 AttributeValue
类。在这里,我们使用 software.amazon.awssdk.services.dynamodb.model
包中的那个(API 文档)。请记住,表存储项目。一个项目由一个或多个属性组成。一个 AttributeValue
保存属性的值。AttributeValue
有一个构建器(API 文档),用于构建 AttributeValue
实例。属性值可以是字符串、数字、二进制数据、列表或集合。您使用与数据类型对应的适当方法来设置 AttributeValue
对象的值。例如,对于 String
使用 s(String value)
,二进制使用 b(SdkBytes b)
,对于 string
的集合,使用 ss(Collection ss)
。有关完整列表,请参阅 API 文档。
AttributeValue
实例被放置在一个 Map
中,其中键是数据库表中的属性名称。Observation
的属性使用适当的构建器方法进行映射。
Observation.id
是一个String
,所以我们使用.s(observation.getStationid() + observation.getDate() + observation.getTime()).build()
image
,尽管已编码,是二进制的,因此我们使用observationMap.put("image", AttributeValue.builder() .b(SdkBytes.fromUtf8String(observation.getImage())).build());
tags
是一个可选的字符串列表,所以我们将其包装在一个条件中并使用if (observation.getTags() != null) { observationMap.put("tags", AttributeValue.builder() .ss(observation.getTags()).build()); }
PutItemRequest
PutItemRequest
包装了发送到 DynamoDBClient
putItem
方法的 JSON 请求。PutItemRequestBuilder
构建一个 PutItemRequest
。上面,我们首先添加了表名,然后是要放置的项目。该项目是一个观测数据属性的键值映射。构建 PutItemRequest
实例后,DynamoDBClient
实例使用该请求将观测数据写入 DynamoDB
Observation
表。
PutItemRequest request = PutItemRequest.builder().tableName("Observation")
.item(observationMap).build();
this.dynamoDbClient.putItem(request);
有关更多信息,请参阅 API 文档。
创建 Rest 端点
- 使用
@Autowired
注解将ObservationService
自动装配到ObservationApiController
。 - 修改
saveObservation
方法以调用服务的writeObservation
方法。package com.tutorial.aws.dynamodb.api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tutorial.aws.dynamodb.model.Observation; import com.tutorial.aws.dynamodb.service.ObservationService; @RestController @RequestMapping(value = "/observations") public class ObservationApiController { @Autowired ObservationService observationService; @PostMapping("/observation") public void saveObservation(@RequestBody Observation observation) { this.observationService.writeObservation(observation); } }
- 构建应用程序并启动 Spring Boot 应用程序。
使用 Postman 进行测试
- 返回到之前在 Postman 中创建的请求,然后单击 发送 按钮。
- 转到 AWS 控制台中的表,然后单击“项目”。
- 如果一切操作正确,您应该会在
Observation
表中看到两个项目。{ "date": { "S": "1999-09-22" }, "id": { "S": "2211999-09-22071244" }, "image": { "B": "LzlqLzRBQ <snip> JRPT0=" }, "stationid": { "N": "221" }, "tags": { "SS": [ "observation", "river", "sample" ] }, "time": { "S": "071244" } }
读取观测数据
现在我们已经将 Observation
写入 DynamoDB
,接下来我们创建一个 Rest 端点来获取一个 Observation
。
修改服务类
- 修改
ObservationService
,使其具有一个getObservation
方法,该方法将观测数据的id
作为参数。 - 创建一个
HashMap
来存储AttributeValue
集合。这些用于查询数据库。 - 将
observationId
添加到HashMap
。 - 构建一个
GetItemRequest
,将HashMap
赋值为键。 - 从响应中获取项目,并使用其值创建一个新的
Observation
。 - 由于
tags
是一个项目列表,因此您必须遍历它们才能将它们添加到Observation
对象实例中。public Observation getObservation(String observationId) { HashMap<String,AttributeValue> key = new HashMap<String,AttributeValue>(); key.put("id", AttributeValue.builder().s(observationId).build()); GetItemRequest request = GetItemRequest.builder() .tableName("Observation").key(key).build(); Map<String,AttributeValue> responseItem = this.dynamoDbClient.getItem(request).item(); Observation observation = new Observation(); observation.setDate(responseItem.get("date").s()); observation.setTime(responseItem.get("time").s()); observation.setImage(responseItem.get("image").b().asUtf8String()); observation.setStationid(Long.parseLong(responseItem .get("stationid").n())); if(responseItem.get("tags") != null && responseItem.get("tags") .ss().size() > 0) { HashSet<String> vals = new HashSet<>(); responseItem.get("tags").ss().stream().forEach(x->vals.add(x)); observation.setTags(vals); } return observation; }
GetItemRequest
GetItemRequest
封装了一个发送到 DynamoDB
的 JSON Get
请求。要获取特定的 Observation
,我们必须向 Get
请求提供 id
。key
是 AttributeValue
项的 Map
。在这种情况下,我们只添加了一个属性,即 id
。
GetItemRequest request = GetItemRequest.builder()
.tableName("Observation").key(key).build();
创建 Rest 端点
- 添加一个名为
getObservation
的方法,该方法将observation
的id
作为路径变量。 - 调用
ObservationService
的getObservation
方法并返回结果。@GetMapping("/observation/{observationid}") public Observation getObservation(@PathVariable("observationid") String observationId) { return this.observationService.getObservation(observationId); }
使用 Postman 进行测试
- 在 Postman 中创建一个新请求,URL 如下。
- 添加之前添加的观测数据中的一个 id。
https://:8080/observations/observation/2211992-03-12091312
- 创建请求后,单击 发送,如果使用了
211992-03-12091312
,则应显示以下响应。{ "stationid": 221, "date": "1992-03-12", "time": "091312", "image": "/9j/4AA <snip> kf/9k=", "tags": [ "rapids", "rocks", "observation", "cold" ] }
删除观测数据
到目前为止,我们已经向 DynamoDB
添加并获取了一个 Observation
。现在我们来删除一个 Observation
。
修改服务类
- 添加一个
deleteObservation
方法,该方法将观测数据的id
作为参数。 - 创建一个
HashMap
来保存属性。 - 构建一个新的
DeleteItemRequest
,并使用HashMap
作为key
。 - 使用
dynamoDbClient
删除观测数据。public void deleteObservation(String observationId) { HashMap<String,AttributeValue> key = new HashMap<>(); key.put("id", AttributeValue.builder().s(observationId).build()); DeleteItemRequest deleteRequest = DeleteItemRequest.builder() .key(key).tableName("Observation").build(); this.dynamoDbClient.deleteItem(deleteRequest); }
DeleteItemRequest
DeleteItemRequest
封装了一个 JSON Delete HTTP 请求。与所有请求一样,我们使用构建器。构建器使用表和键来删除 Observation
。
创建 Rest 端点
- 创建一个新的 Rest 端点来删除观测数据。
- 将观测数据的
id
作为路径变量传递给端点,并且只在变量后添加/delete
。 - 调用
ObservationService
的deleteObservation
方法。@DeleteMapping("/observation/{observationid}/delete") public void deleteObservation(@PathVariable("observationid") String observationId) { this.observationService.deleteObservation(observationId); }
使用 Postman 进行测试
- 使用 Postman 创建一个新
Request
。 - 从下拉列表中选择 DELETE 以表示这是一个 Http Delete 请求。
https://:8080/observations/observation/2211992-03-12091312/delete
- 单击 发送,记录应该会被删除。导航到 AWS 控制台中的项目以确保
Observation
已删除。
更新观测数据
一个 Observation
可以有一个或多个 tags
。这似乎很可能在以后添加和/或修改。让我们创建一个端点,允许添加/修改观测数据的标签。
更新服务类
- 创建一个名为
updateObservationTags
的方法,该方法将标签列表和观测数据id
作为参数。 - 创建一个
HashMap
来保存AttributeValue
对象。 - 使用
AttributeBuilderValue
构建器将标签添加到HashMap
中,并将:tagval
作为键。 - 创建第二个
HashMap
来保存observation
的 ID。 - 构建一个使用
update
表达式的UpdateItemRequest
。public void updateObservationTags(List<String> tags, String observationId) { HashMap<String, AttributeValue> tagMap = new HashMap<String, AttributeValue>(); tagMap.put(":tagval", AttributeValue.builder().ss(tags).build()); HashMap<String, AttributeValue> key = new HashMap<>(); key.put("id", AttributeValue.builder().s(observationId).build()); UpdateItemRequest request = UpdateItemRequest.builder() .tableName("Observation").key(key) .updateExpression("SET tags = :tagval") .expressionAttributeValues(tagMap).build(); this.dynamoDbClient.updateItem(request); }
UpdateItemRequest
DynamoDBClient
实例使用 UpdateItemRequest
构建更新项目的请求。与获取和删除一样,它需要一个键来正确选择正确的项目。但它还需要更新的值。您提供一个更新表达式,然后提供属性。请注意,属性的键 :tagval
与表达式匹配。然后,请求使用键和 update
表达式来更新项目。
添加 Rest 端点
- 添加一个端点,该端点将
observationid
作为路径变量,并将 JSON 标签数组作为请求正文。 - 调用
ObservationService
的updateObservationTags
方法@PostMapping("/observation/{observationid}/updatetags") public void updateObservationTags(@PathVariable("observationid") String observationId, @RequestBody List<String> tags) { this.observationService.updateObservationTags(tags, observationId); }
使用 Postman 进行测试
- 创建新的 Http Post 请求,URL 如下。
- 将
id
值直接作为路径参数添加到 URL 中。https://:8080/observations/observation/2211992-03-12091312/updatetags
- 将以下值作为
Body
添加,并将其类型指定为JSON (application/json)
。["observation","rocks","rapids","cold"]
- 点击 发送,然后导航到 AWS 控制台查看观测数据。
Observation
应该已添加了标签。
批量写入观测数据
有时,需要将多个项目写入数据库。
更新服务类
public void batchWriteObservations(List<Observation> observations) {
ArrayList<WriteRequest> requests = new ArrayList<>();
HashMap<String, AttributeValue> observationMap = new HashMap<>();
for(Observation observation : observations) {
observationMap.put("id", AttributeValue.builder()
.s(observation.getStationid() + observation.getDate() +
observation.getTime()).build());
observationMap.put("stationid", AttributeValue.builder()
.n(Long.toString(observation.getStationid())).build());
observationMap.put("date", AttributeValue.builder()
.s(observation.getDate()).build());
observationMap.put("time", AttributeValue.builder()
.s(observation.getTime()).build());
observationMap.put("image", AttributeValue.builder()
.b(SdkBytes.fromUtf8String(observation.getImage())).build());
if (observation.getTags() != null) {
observationMap.put("tags", AttributeValue.builder()
.ss(observation.getTags()).build());
}
WriteRequest writeRequest = WriteRequest.builder()
.putRequest(PutRequest.builder().item(observationMap)
.build()).build();
requests.add(writeRequest);
}
HashMap<String,List<WriteRequest>> batchRequests = new HashMap<>();
batchRequests.put("Observation", requests);
BatchWriteItemRequest request = BatchWriteItemRequest.builder()
.requestItems(batchRequests).build();
this.dynamoDbClient.batchWriteItem(request);
}
DynamoDbClient batchWriteItem
方法将 BatchWriteItemRequest 作为参数。BatchWriteItem
可以一次写入或删除最多 25 个项目,并且数据量限制为 16 MB。请注意,它仍然会进行与您拥有的项目一样多的调用;但是,它会并行进行这些调用。
您创建一个 List
来保存每个 Observation
的 WriteRequest
。每个 Observation
都以键值对的形式写入 Map
。该映射被添加到 WriteRequest
中,然后 WriteRequest
被添加到列表中,直到所有观测数据都准备好作为 WriteRequest
实例。
WriteRequest writeRequest = WriteRequest.builder()
.putRequest(PutRequest.builder().item(observationMap)
.build()).build();
每个 WriteRequest
实例列表都被添加到另一个映射中。表名是键,列表是值。通过这种方式,一次批量写入可以写入多个表。创建 WriteRequest
实例列表的映射后,整个内容用于创建 BatchWriteItemRequest
,然后由 DynamoDbClient batchWriteItem
方法使用。
HashMap<String,List<WriteRequest>> batchRequests = new HashMap<>();
batchRequests.put("Observation", requests);
BatchWriteItemRequest request = BatchWriteItemRequest.builder()
.requestItems(batchRequests).build();
创建 Rest 端点
- 添加一个 Rest 端点,该端点将观测数据列表作为 JSON 放置在
request
正文中。 - 调用
ObservationService
中的batchWriteObservations
方法。@PostMapping("/observation/batch") public void batchSaveObservation(@RequestBody List<Observation> observations) { this.observationService.batchWriteObservations(observations); }
- 构建并运行应用程序。
使用 Postman 进行测试
- 在 Postman 中创建一个新的 Post 请求,URL 如下。
https://:8080/observations/observation/batch
- 将以下内容添加到 Body 中,并将其类型指定为
JSON (application/json)
。[ { "stationid": 221, "date": "2007-12-12", "time": "180000", "image": "/9j/4AAQSkZJRgABAQA <snip> kf/9k=" }, { "stationid": 221, "date": "2009-05-22", "time": "043455", "image": "/9j/4AAQSkZJRgABAQAA <snip> /8AD9KhoA//2Q==" }, { "stationid": 234, "date": "2019-10-18", "time": "121459", "image": "/9j/4AAQSkZJRgABAQA <snip> VWoGf/9k=" }, { "stationid": 234, "date": "2017-09-22", "time": "093811", "image": "/9j/4AAQSkZJRgAB <snip> 5//2Q==" } ]
- 点击 发送,然后导航到 AWS 控制台观测表中的项目,观测数据应该已添加。
有条件地获取观测数据
一个常见的需求是根据特定条件获取记录。例如,假设我们希望获取属于特定站点的所有观测数据。使用 DynamoDB
时,用于查询的任何变量都必须被索引。因此,在创建查询之前,我们首先在 Observation
表的 stationid
变量上创建索引。
创建索引
- 导航到 AWS 控制台中的
Observation
表。 - 点击 创建索引。
- 选择
stationid
作为索引的分区键,并确保将其定义为 Number 类型。 - 点击 创建索引 以创建索引。
二级索引
二级索引允许使用除主键之外的属性从表中检索数据。您从索引而不是表中检索数据。有关 DynamoDB
二级索引的更多信息,请参阅 LinuxAcademy 的以下文章:DynamoDB 二级索引快速指南。
更新服务类
public List<Observation> getObservationsForStation(String stationId){
ArrayList<Observation> observations = new ArrayList<>();
Condition condition = Condition.builder()
.comparisonOperator(ComparisonOperator.EQ)
.attributeValueList(AttributeValue.builder()
.n(stationId).build()).build();
Map<String, Condition> conditions = new HashMap<String, Condition>();
conditions.put("stationid",condition);
QueryRequest request = QueryRequest.builder().tableName("Observation")
.indexName("stationid-index").keyConditions(conditions).build();
List<Map<String, AttributeValue>> results = this.dynamoDbClient
.query(request).items();
for(Map<String,AttributeValue> responseItem: results) {
Observation observation = new Observation();
observation.setDate(responseItem.get("date").s());
observation.setTime(responseItem.get("time").s());
observation.setImage(responseItem.get("image").b().asUtf8String());
observation.setStationid(Long.parseLong(
responseItem.get("stationid").n()));
if(responseItem.get("tags") != null && responseItem.get("tags").ss()
.size() > 0) {
HashSet<String> vals = new HashSet<>();
responseItem.get("tags").ss().stream().forEach(x->vals.add(x));
observation.setTags(vals);
}
observations.add(observation);
}
return observations;
}
首先,我们使用其关联的构建器创建了一个 Condition
。条件是“=<传递给函数的站点 ID>
”。
Condition condition = Condition.builder()
.comparisonOperator(ComparisonOperator.EQ)
.attributeValueList(AttributeValue.builder()
.n(stationId).build()).build();
然后我们将 Condition
添加到一个映射中,并指定 stationid
为键,condition
为值。然后我们使用其关联的构建器构建了 QueryRequest
。
QueryRequest request = QueryRequest.builder().tableName("Observation")
.indexName("stationid-index").keyConditions(conditions).build();
创建 Rest 端点
- 创建一个新的 Rest 端点,将
stationid
指定为path
变量。 - 调用
ObservationService
的getObservationsForStation
方法。@GetMapping("/station/{stationid}") public List<Observation> getObservations(@PathVariable("stationid") String stationId) { return this.observationService.getObservationsForStation(stationId); }
- 构建并运行应用程序。
使用 Postman 进行测试
- 在 Postman 中创建一个新的
Get
请求,其中包含 URL 中的站点 ID。 - 单击 发送,然后站点 221 的
Observation
项目应显示为响应正文。https://:8080/observations/station/221
[ { "stationid": 221, "date": "2009-05-22", "time": "043455", "image": "/9j/4AA <snip> 0/8AD9KhoA//2Q==", "tags": null }, { "stationid": 221, "date": "2007-12-12", "time": "180000", "image": "/9j/4 <snip> /rn+q07/sHxfyNUK0Wxkf/9k=", "tags": null }, { "stationid": 221, "date": "1992-03-12", "time": "091312", "image": "/9j/4AAQSkZJRgABAQAAYAB <snip> K0Wxkf/9k=", "tags": [ "rapids", "rocks", "observation", "cold" ] }, { "stationid": 221, "date": "1999-09-22", "time": "071244", "image": "/9j/4n0g27Qu <snip> A//2Q==", "tags": [ "observation", "river", "sample" ] } ]
进一步的主题
本教程中未探讨几个主题。首先,您可以扫描数据库表。扫描表时,将返回表中的所有项目。其次,本教程未讨论有条件地更新或删除项目。但是,原理与有条件地查询表中的项目相同。此外,探索使用 DynamoDB
的命令行示例也很有帮助,因为它们有助于理解 SDK。最后,我们没有涵盖 Java 1.1 AWS SDK。
从 Java 1.1 AWS SDK 到 Java 2 AWS SDK
网上有许多使用 Java 1.1 API 而不是 Java 2 API 的示例和教程。但是,这两个版本的主要区别在于构建器模式。许多(如果不是大多数)Java 1.1 教程仍然有用。模式是相同的:
- 创建一个请求类型
- 使用所需参数设置请求
- 将请求传递给
DynamoDB
客户端 - 获取结果
在 Java 1.1 SDK 中,您使用构造函数、setter 和 getter 执行这些步骤。在 Java 2 SDK 中,您使用构建器。Java 2 AWS SDK 中的几乎所有类都使用构建器。如果您有一个特别好的使用 Java 1.1 SDK 的教程,请将其作为起点。虽然并非万无一失,但这样做帮助我将许多 Java 1.1 示例转换为 Java 2 SDK。
更多资源
- TutorialPoint DynamoDB 教程
- 亚马逊文档
- 适用于 Java 2 的 AWS SDK 开发人员指南
- API 2.5.41 Java API 参考
- 如何使用 DynamoDB 全局二级索引来提高查询性能并降低成本
- 更多关于 DynamoDB 设计模式的信息。
本教程虽然使用了 Java 1 AWS API,但它是一个很好的入门教程,涵盖了本教程中的相同主题。请记住,要想到构建器,尽管 API 中的技术相同,但 Java 2 版本的 API 大量使用了构建器。
结论
在本教程中,我们探讨了 Java 2 SDK 的低级 API。我们写入了一个项目,更新了一个项目,删除了一个项目,并批量上传了项目。我们还探讨了有条件地查询项目。
与所有 SDK 一样,它基于构建器、请求和客户端。您构建一个请求,将其传递给 DynamoDBClient
,然后 DynamoDBClient
返回一个响应。您不会创建一个新的请求实例并通过 setter 设置属性,而是使用构建器来构建请求。
DynamoDB
是一个非关系型数据库,因此您不能在任何字段上直接编写条件查询。您只能在已建立索引的字段上使用查询。如果您考虑到 DynamoDB
是为大量相对非结构化数据设计的,这似乎是合乎逻辑的。
Github 项目
历史
- 2019 年 5 月 19 日:初始版本