1. 概述
NoSQL 數據庫已成為構建應用程序持久層的一種流行選擇。
Amazon DynamoDB 便是 Amazon Web Services (AWS) 提供的、一種服務器端管理型 NoSQL 數據庫。 過去十年裏,由於其可擴展性、靈活性和性能,DynamoDB 已成為雲端中最受歡迎和廣泛使用的 NoSQL 數據庫之一。
與 DynamoDB 交互的核心組件是表、項目和屬性。 一個表是項目集合,而每個項目都是屬性集合。
在本教程中,我們將探索如何將 Amazon DynamoDB 集成到 Spring Boot 應用程序中。
2. 設置項目
在開始與 Amazon DynamoDB 服務交互之前,我們需要包含必要的依賴項並正確配置我們的應用程序。
2.1. 依賴關係
我們將使用 Spring Cloud AWS 與 DynamoDB 服務建立連接並進行交互,而不是直接使用 AWS 提供的 DynamoDB SDK。 Spring Cloud AWS 是圍繞官方 AWS SDK 構建的封裝,它極大地簡化了配置並提供了簡單的方法來與 AWS 服務進行交互。
接下來,讓我們在項目的 pom.xml 文件中添加 DynamoDB starter 依賴:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-dynamodb</artifactId>
<version>3.3.0</version>
</dependency>接下來,我們還將包含 Spring Cloud AWS BOM(物料清單),以管理 DynamoDB starter 的版本,在我們的 pom.xml 中:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws</artifactId>
<version>3.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>通過此添加,我們現在可以從我們的啓動依賴項中移除 version 標籤。
BOM 確保了聲明的依賴項之間的版本兼容性,避免衝突,並使將來更新依賴項版本更加容易。
2.2. 定義 AWS 配置屬性
現在,為了與 DynamoDB 服務交互,我們需要配置我們的 AWS 憑據以進行身份驗證,以及我們在其中配置了表的 AWS 區域。
我們將配置這些屬性在我們的 application.yaml 文件中:
spring:
cloud:
aws:
dynamodb:
region: ${AWS_REGION}
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}我們使用 ${} 屬性佔位符從環境變量中加載我們的屬性值。
2.3 領域實體
現在,讓我們定義一個簡單的 User 實體類,它代表了我們 DynamoDB 表的數據模型:
@DynamoDbBean
public class User {
private UUID id;
private String name;
private String email;
@DynamoDbPartitionKey
public UUID getId() {
return id;
}
// standard setters and getters
}在此,我們為 User 類添加了 @DynamoDbBean 註解,將其標記為可映射到 DynamoDB 表的實體。請注意,Spring Cloud AWS 在使用 package-private 修飾符時,無法映射表屬性,因此 User 實體及其對應的 getter 和 setter 方法必須聲明為 public。
此外,我們配置了 id 字段作為分區鍵,即為我們的表的主鍵,通過在 getId() 方法上添加 @DynamoDbPartitionKey 註解來實現。請務必將此註解放置在 getter 方法上,而不是字段本身。
DynamoDB 還支持通過使用可選的 排序鍵 創建複合主鍵。可以通過在關聯字段的 getter 方法上添加 @DynamoDbSortKey 註解來定義複合主鍵。在本教程中,我們不會使用排序鍵,但瞭解它們的存在是有益的。
3. 定義自定義 DynamoDbTableNameResolver Bean
默認情況下,Spring Cloud AWS 會將實體類名轉換為蛇形命名錶示形式以確定相應的 DynamoDB 表名。然而,這種約定可能並不總是與我們的命名約定或要求一致。
我們可以定義一個自定義 Bean,該 Bean 實現 DynamoDbTableNameResolver 接口,以覆蓋此默認行為。
讓我們從創建一個自定義註解開始,以便在實體類上直接指定表名:
@Target(TYPE)
@Retention(RUNTIME)
@interface TableName {
String name();
}我們創建一個簡單的 @TableName 註解,它接受一個名為 name 的屬性,其中我們可以指定所需的 DynamoDB 表名。
接下來,讓我們創建我們的自定義 DynamoDbTableNameResolver 實現:
@Component
class CustomTableNameResolver implements DynamoDbTableNameResolver {
@Override
public <T> String resolve(Class<T> clazz) {
return clazz.getAnnotation(TableName.class).name();
}
}在這裏,我們覆蓋了 resolve()方法,在我們的 CustomTableNameResolver類中。我們檢索了應用於實體類的 @TableName註解的 name屬性值,並將其用作表名。
最後,讓我們用新的 @TableName註解標註我們的 User類:
@DynamoDbBean
@TableName(name = "users")
class User {
// ...
}使用此配置時,Spring Cloud AWS 將使用我們的 CustomTableNameResolver 類來確定 User 實體映射到 DynamoDB 中的 users 表
4. 使用 LocalStack 設置本地測試環境
在開發過程中,本地測試應用程序通常非常方便。 LocalStack 是一個流行的工具,它允許我們在本地機器上模擬運行 AWS 環境。 我們將使用 Testcontainers 來在應用程序中設置 LocalStack 服務。
運行 LocalStack 服務並通過 Testcontainers 運行所需的先決條件是具有活動 Docker 實例。 無論是在本地運行測試套件還是在 CI/CD 管道中使用,都需要確保滿足此先決條件。
4.1. 測試依賴
首先,我們需要將必要的測試依賴添加到我們的 <em >pom.xml</em> 中:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>localstack</artifactId>
<scope>test</scope>
</dependency>我們導入了 Spring Cloud AWS Testcontainers 依賴項以及 Testcontainers 的 LocalStack 模塊。 這些依賴項為我們提供了用於啓動 LocalStack 服務的臨時 Docker 實例所需的類。
4.2. 使用 Init 鈎子配置 DynamoDB 表
接下來,我們需要配置一個 DynamoDB 表,供我們的應用程序進行交互。 Localstack 提供了通過啓動容器時使用 初始化鈎子 創建所需 AWS 資源的權限。
讓我們在 src/test/resources 文件夾中為此目的創建一個 <em >init-dynamodb-table.sh</em > bash 腳本:
#!/bin/bash
table_name="users"
partition_key="id"
awslocal dynamodb create-table
--table-name "$table_name"
--key-schema AttributeName="$partition_key",KeyType=HASH
--attribute-definitions AttributeName="$partition_key",AttributeType=S
--billing-mode PAY_PER_REQUEST
echo "DynamoDB table '$table_name' created successfully with partition key '$partition_key'"
echo "Executed init-dynamodb-table.sh"上述腳本創建了一個名為 users 的 DynamoDB 表。 我們使用在 shell 腳本中運行的 awslocal 命令,它是一個圍繞 AWS CLI 的包裝器,指向 LocalStack 服務。 腳本的結尾我們使用 echo 語句來確認腳本的成功執行。
我們將此腳本複製到 LocalStack 容器中的 /etc/localstack/init/ready.d 路徑以供在下一部分中執行。
4.3. 定義 LocalStackContainer Bean
接下來,讓我們創建一個 @TestConfiguration 類,定義我們的 Testcontainers Bean:
@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
@Bean
@ServiceConnection
LocalStackContainer localStackContainer() {
return new LocalStackContainer(DockerImageName.parse("localstack/localstack:4.3.0"))
.withServices(LocalStackContainer.Service.DYNAMODB)
.withCopyFileToContainer(
MountableFile.forClasspathResource("init-dynamodb-table.sh", 0744),
"/etc/localstack/init/ready.d/init-dynamodb-table.sh"
)
.waitingFor(Wait.forLogMessage(".*Executed init-dynamodb-table.sh.*", 1));
}
}我們在創建我們的 LocalStackContainer Bean 時,指定了本地版本的 LocalStack Docker 鏡像。
然後,我們啓用 DynamoDB 服務,並將我們的 Bash 腳本複製到容器中,以確保表創建。此外,我們配置了一種策略,等待 Executed init-dynamodb-table.sh 語句被打印,正如我們在 init 腳本中定義的。
我們還使用 @ServiceConnection 註解對 Bean 方法進行註釋,這會動態註冊所有設置與啓動的 LocalStack 容器連接所需的屬性。現在,我們可以通過使用 TestcontainersConfiguration.class 註解對測試類進行註釋來使用此配置。
5. 與我們的 DynamoDB 表交互
現在我們已經配置了本地環境,讓我們使用 Spring Cloud AWS 自動為我們創建的 DynamoDbTemplate Bean,以便與我們提供的 users 表進行交互。
5.1. 執行基本 CRUD 操作
首先,讓我們在提供的 DynamoDB 表中創建一個新的 User 項目:
User user = Instancio.create(User.class);
dynamoDbTemplate.save(user);
Key partitionKey = Key.builder().partitionValue(user.getId().toString()).build();
User retrievedUser = dynamoDbTemplate.load(partitionKey, User.class);
assertThat(retrievedUser)
.isNotNull()
.usingRecursiveComparison()
.isEqualTo(user);我們使用 Instancio 創建一個帶有隨機測試數據的 User 對象。然後,我們使用 DynamoDbTemplate 類的 save() 方法將其持久化到我們的配置表中。
為了驗證此操作,我們使用用户的分區鍵值創建 Key 對象,並將它傳遞給 load() 方法。然後,我們斷言檢索到的 retrievedUser 與我們持久化的 user 匹配。
接下來,讓我們更新一個現有的 User 項目:
String updatedName = RandomString.make();
String updatedEmail = RandomString.make();
user.setName(updatedName);
user.setEmail(updatedEmail);
dynamoDbTemplate.update(user);
Key partitionKey = Key.builder().partitionValue(user.getId().toString()).build();
User updatedUser = dynamoDbTemplate.load(partitionKey, User.class);
assertThat(updatedUser.getName())
.isEqualTo(updatedName);
assertThat(updatedUser.getEmail())
.isEqualTo(updatedEmail);在這裏,我們更新了已持久化的 User 對象中的 name 和 email 屬性。 我們調用 DynamoDbTemplate 類的 update() 方法來持久化這些更改。 我們通過檢索更新後的用户來斷言屬性已正確修改。
最後,讓我們從表中刪除一個 User 項目:
dynamoDbTemplate.delete(user);
Key partitionKey = Key.builder().partitionValue(user.getId().toString()).build();
User deletedUser = dynamoDbTemplate.load(partitionKey, User.class);
assertThat(deletedUser)
.isNull();我們調用 DynamoDbTemplate 上的 delete() 方法,並將要刪除的 User 對象作為參數傳遞。然後,我們嘗試使用相同的分區鍵檢索已刪除的對象,並斷言它不再存在於表中。
5.2. 執行掃描操作
我們已經知道,可以使用 load() 方法從 DynamoDbTemplate bean 中檢索使用分區鍵的項目。
但是,我們可能還需要檢索多個項目或根據非鍵屬性對它們進行過濾。DynamoDB 提供掃描操作來實現這一點。
首先,讓我們看看如何從配置的表中檢索所有 User 項目:
int numberOfUsers = 10;
for (int i = 0; i < numberOfUsers; i++) {
User user = Instancio.create(User.class);
dynamoDbTemplate.save(user);
}
List<User> retrievedUsers = dynamoDbTemplate
.scanAll(User.class)
.items()
.stream()
.toList();
assertThat(retrievedUsers.size())
.isEqualTo(numberOfUsers);我們將多個 User 對象保存到我們的表中,然後使用 scanAll() 方法檢索所有項。我們斷言檢索到的用户數量與我們最初保存的用户數量匹配。
默認情況下,如果查詢結果超過 1MB,DynamoDB 會分頁響應並返回一個令牌以檢索下一頁。但是,Spring Cloud AWS 在後台自動處理分頁,並返回表中所有保存的項。
此外,我們可以使用過濾表達式執行掃描操作以檢索特定項:
Expression expression = Expression.builder()
.expression("#email = :email")
.putExpressionName("#email", "email")
.putExpressionValue(":email", AttributeValue.builder().s(user.getEmail()).build())
.build();
ScanEnhancedRequest scanRequest = ScanEnhancedRequest
.builder()
.filterExpression(expression)
.build();
User retrievedUser = dynamoDbTemplate.scan(scanRequest, User.class)
.items()
.stream()
.findFirst()
.get();
assertThat(retrievedUser)
.isNotNull()
.usingRecursiveComparison()
.isEqualTo(user);在這裏,我們創建一個 Expression 對象,其中包含一個過濾器條件,該條件與特定 User 的 email 屬性匹配。我們將這個 expression 對象包裹在一個 ScanEnhancedRequest 對象中,並將其傳遞給 scan() 方法。最後,我們斷言檢索到的 retrievedUser 與我們最初保存的 user 匹配。
6. IAM 權限
我們使用了 LocalStack 模擬器進行演示。但是,在與真實的 DynamoDB 服務交互時,我們需要將以下 IAM 策略分配給我們在應用程序中配置的 IAM 用户:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:GetItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Scan"
],
"Resource": "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/users"
}
]
}在此,我們授予應用程序在 users 表上執行特定操作的權限。 我們的 IAM 策略符合 最小權限原則,僅授予應用程序正確運行所需的必要權限。
我們應該記住將 REGION 和 ACCOUNT_ID 佔位符替換為實際值,在 Resource ARN 中。
7. 結論
在本文中,我們探討了如何將 Amazon DynamoDB 集成到 Spring Boot 應用程序中,使用 Spring Cloud AWS。
我們完成了必要的配置,定義了數據模型以及自定義表名解析器。然後,我們使用 Testcontainers 啓動了一個臨時的 Docker 容器,用於 LocalStack 服務,從而創建了一個本地測試環境。
最後,我們使用 <em DynamoDbTemplate</em> 執行了基本 CRUD 操作和掃描操作,對我們提供的 DynamoDB 表進行了操作,並討論了所需的 IAM 權限。
如往常一樣,本文中使用的所有代碼示例都可以在 GitHub 上找到。