知識庫 / Spring / Spring Boot RSS 訂閱

使用 Spring Cloud AWS 集成 Amazon DynamoDB

NoSQL,Spring Boot,Spring Cloud
HongKong
7
02:44 PM · Dec 06 ,2025

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 對象中的 nameemail 屬性。 我們調用 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 對象,其中包含一個過濾器條件,該條件與特定 Useremail 屬性匹配。我們將這個 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 策略符合 最小權限原則,僅授予應用程序正確運行所需的必要權限。

我們應該記住將 REGIONACCOUNT_ID 佔位符替換為實際值,在 Resource ARN 中。

7. 結論

在本文中,我們探討了如何將 Amazon DynamoDB 集成到 Spring Boot 應用程序中,使用 Spring Cloud AWS。

我們完成了必要的配置,定義了數據模型以及自定義表名解析器。然後,我們使用 Testcontainers 啓動了一個臨時的 Docker 容器,用於 LocalStack 服務,從而創建了一個本地測試環境。

最後,我們使用 <em DynamoDbTemplate</em> 執行了基本 CRUD 操作和掃描操作,對我們提供的 DynamoDB 表進行了操作,並討論了所需的 IAM 權限。

如往常一樣,本文中使用的所有代碼示例都可以在 GitHub 上找到

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.