[Spring] DynamoDB Enhanced Client 사용 기 (feat. AWS SDK V2)

저번 글에서는 Spring data를 사용하여 DynamoDB에 대한 CRUD를 구현 했다. Spring data프로젝트에서 DynamoDb에 대한 지원은 하지 않고 있고 안정적인 방식은 AWS에서 제공하는 SDK를 사용하는 것이다. V1의 DynamoDBMapper를 사용한 예제는 많으나 V2의 Enhance Client를 사용하여 구현한 가이드를 찾기 힘들어 정리하려고 한다.

Maven 설저

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-bom</artifactId>
                <version>1.11.1000</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-dynamodb</artifactId>
    </dependency>
<dependencies>

AWS SDK에서 dependency를 관리해주는 aws-java-sdk-bom를 추가해 주고 밑 부분에 DynamoDB에 대한 의존성을 추가 해준다.

Configuration

@Configuration
public class DynamoDbconfig {

    @Value("${amazon.aws.accesskey}")
    private String amazonAWSAccessKey;

    @Value("${amazon.aws.secretkey}")
    private String amazonAWSSecretKey;

    @Bean
    public DynamoDbClient dynamoDbClient(){
        return DynamoDbClient.builder()
                .region(Region.US_EAST_1)
                .credentialsProvider(
                        StaticCredentialsProvider.create(
                                AwsBasicCredentials.create(amazonAWSAccessKey, amazonAWSSecretKey)
                        )
                ).build();
    }

    @Bean
    public DynamoDbEnhancedClient dynamoDbEnhancedClient(
            @Qualifier("dynamoDbClient") DynamoDbClient dynamoDbClient
    ){
        return DynamoDbEnhancedClient.builder()
                .dynamoDbClient(dynamoDbClient).build();
    }
}

Data Entity

@DynamoDBTable(tableName = "product")
@DynamoDbBean
public class Product implements Serializable {

    private String id;
    private String pk;
    private String name;
    private String creatAt;
    private double price;
    private String detail;

    @DynamoDBAttribute(attributeName = "id")
    public String getId() { return id;}
    public void setId(String id){ this.id = id;}

    @DynamoDbPartitionKey()
    @DynamoDBAttribute(attributeName = "PK")
    public String getPk() {
        return pk;
    }
    public void setPK(String pk){ this.pk = pk;}

    @DynamoDBAttribute(attributeName = "name")
    public String getName() { return name;}
    public void setName(String symbol){ this.name = symbol; }

    @DynamoDbSortKey
    @DynamoDBAttribute(attributeName = "creatAt")
    public String getCreatAt() { return  creatAt;}
    public void setCreatAt(String creatAt){ this.creatAt = creatAt;}

    @DynamoDBAttribute(attributeName = "price")
    public double getPrice() { return price; }
    public void setPrice(double price){ this.price = price;}

    @DynamoDBAttribute(attributeName = "detail")
    public String getDetail() { return detail; }
    public void  setDetail(String detail){ this.detail = detail;}
}

여기서 주의할점은 아래와 같다.

  • 생성자를 만들지 말것, 생성자를 만들면 Enhance client와 연동 시 생성자를 만들지 말라고 에러를 보낸다.(Runtime error)
  • Partition키를 변수가 아닌 get method로 인식하므로 테이블을 외부에서 생성했다면 같은 이름을 써야 한다.(대소문자 동일하게 해야함)
    Attempt to execute an operation against an index that requires a partition key without assigning a partition key to that index.(위 코드를 getPk()로 했을 경우 발생)

CRUD Repository

public class ProductRepository {
    private final DynamoDbEnhancedClient dynamoDbEnhancedClient;
    private final DynamoDbTable<Product> productDynamoDbTable;

    public ProductRepository(DynamoDbEnhancedClient dynamoDbEnhancedClient) {
        this.dynamoDbEnhancedClient = dynamoDbEnhancedClient;
        this.productDynamoDbTable = this.dynamoDbEnhancedClient.table("Product",
                TableSchema.fromBean(Product.class));
    }

public Product findByNameOrderByCreateDesc(String name) {

        QueryConditional queryConditional = QueryConditional
                .keyEqualTo(Key.builder().partitionValue(name)
                        .build());
        QueryEnhancedRequest queryEnhancedRequest= QueryEnhancedRequest.builder()
                .queryConditional(queryConditional)
                .scanIndexForward(false)
                .build();

        return productDynamoDbTable.query(queryEnhancedRequest)
                .items()
                .stream()
                .findFirst()
                .get();
}

public List<Product> findByNameBetweenTimestamp(String name, String fomTime, String toTime){
        Key fromKey = Key.builder().partitionValue(name).sortValue(fomTime).build();
        Key toKey = Key.builder().partitionValue(name).sortValue(toTime).build();

        QueryConditional sortValueInBetween = QueryConditional
                .sortBetween(fromKey,toKey);

        QueryEnhancedRequest queryEnhancedRequest= QueryEnhancedRequest.builder()
                .queryConditional(sortValueInBetween)
                .scanIndexForward(true)
                .build();

        return productDynamoDbTable.query(queryEnhancedRequest)
                .items()
                .stream()
                .limit(100)
                .collect(Collectors.toList());
}

//Batch성 쓰기 
public void putItemByBatch(List<Product> products) {
   WriteBatch.Builder<SpotPrice> wb = WriteBatch.builder(SpotPrice.class).mappedTableResource(productDynamoDbTable);
   products.forEach(i -> wb.addPutItem(r -> r.item(i)));
   BatchWriteItemEnhancedRequest batchWriteItemEnhancedRequest =
                BatchWriteItemEnhancedRequest.builder()
                        .writeBatches(wb.build()).build();
   dynamoDbEnhancedClient.batchWriteItem(batchWriteItemEnhancedRequest);
}
  • Batch성으로 item을 넣을때 Batch의 크기는 16MB를 초과할 수 없으며, 최대 아이템 개수는 25개보다 적어야 한다.참조

ref

https://jsonobject.tistory.com/597

https://jsonobject.tistory.com/598

https://jsonobject.tistory.com/599

https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb

https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html