DynamoDB 향상된 클라이언트 API의 기본 사항 알아보기 - AWS SDK for Java 2.x

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

DynamoDB 향상된 클라이언트 API의 기본 사항 알아보기

이 항목에서는 DynamoDB 향상된 클라이언트 API의 기본 기능을 설명하고 이를 표준 DynamoDB 클라이언트 API와 비교합니다.

DynamoDB 향상된 클라이언트 API를 처음 사용하는 경우 입문 자습서를 통해 기본 클래스를 익히는 것이 좋습니다.

Java에서 DynamoDB 항목

DynamoDB 테이블은 항목을 저장합니다. 사용 사례에 따라 Java 측 항목은 정적으로 구조화된 데이터 또는 동적으로 생성된 구조의 형태를 취할 수 있습니다.

사용 사례에서 일관된 속성 집합을 가진 항목을 요구하는 경우 주석이 달린 클래스를 사용하거나 빌더를 사용하여 적절한 정적 형식의 TableSchema을 생성하세요.

또는 다양한 구조로 구성된 항목을 저장해야 하는 경우 DocumentTableSchema를 생성하세요. DocumentTableSchema향상된 문서 API 일부이며 정적으로 입력된 기본 키만 필요하며 EnhancedDocument 인스턴스와 함께 작동하여 데이터 요소를 보관합니다. 향상된 문서 API에 대해서는 다른 항목에서 다룹니다.

데이터 모델 클래스의 속성 유형

DynamoDB는 Java의 다양한 형식 시스템에 비해 적은 수의 속성 유형을 지원하지만 DynamoDB 향상된 클라이언트 API는 Java 클래스의 멤버를 DynamoDB 속성 유형으로 또는 DynamoDB 속성 유형에서 변환하는 메커니즘을 제공합니다.

Java 데이터 클래스의 속성 유형 (속성) 은 프리미티브가 아닌 객체 유형이어야 합니다. 예를 들어, 항상 Integer 객체 데이터 유형을 Long 사용하고 프리미티브는 사용하지 마십시오long. int

기본적으로 DynamoDB 향상된 클라이언트 API는 정수, 문자열, 인스턴트와 같은 다양한 유형에 대한 속성 변환기를 지원합니다. BigDecimal 목록은 인터페이스의 알려진 구현 클래스에 표시됩니다. AttributeConverter 목록에는 지도, 목록, 세트 등 다양한 유형과 컬렉션이 포함됩니다.

기본적으로 지원되지 않거나 JavaBean 규칙을 준수하지 않는 속성 유형에 대한 데이터를 저장하려면 변환을 수행하는 사용자 지정 AttributeConverter 구현을 작성할 수 있습니다. 예제는 속성 변환 단원을 참조하세요.

클래스가 Java Beans 사양을 준수하는 속성 유형(또는 변경할 수 없는 데이터 클래스)의 데이터를 저장하려면 두 가지 방법을 사용할 수 있습니다.

  • 소스 파일에 액세스할 수 있는 경우 @DynamoDbBean(또는 @DynamoDbImmutable)로 클래스에 주석을 달 수 있습니다. 중첩 속성에 대해 설명하는 단원에서는 주석이 달린 클래스를 사용하는 예제를 보여줍니다.

  • 속성에 대한 JavaBean 데이터 클래스의 원본 파일에 액세스할 수 없는 경우 (또는 액세스 권한이 있는 클래스의 소스 파일에 주석을 달고 싶지 않은 경우) 에는 빌더 접근 방식을 사용할 수 있습니다. 이렇게 하면 키를 정의하지 않고 테이블 스키마가 생성됩니다. 그런 다음 이 테이블 스키마를 다른 테이블 스키마에 중첩하여 매핑을 수행할 수 있습니다. 중첩 속성 단원에는 중첩 스키마 사용을 보여주는 예제가 있습니다.

Null 값

putItem API를 사용할 때 향상된 클라이언트는 매핑된 데이터 객체의 null 값 속성을 DynamoDB에 대한 요청에 포함하지 않습니다.

updateItem 요청의 경우 null 값 속성은 데이터베이스의 항목에서 제거됩니다. 일부 속성값은 업데이트하고 다른 속성값은 변경하지 않으려면 변경해서는 안 되는 다른 속성의 값을 복사하거나 업데이트 빌더의 ignoreNull() 메서드를 사용하세요.

다음 예제는 the updateItem() 메서드에 대한 ignoreNulls()를 보여줍니다.

public void updateItemNullsExample(){ Customer customer = new Customer(); customer.setCustName("CustName"); customer.setEmail("email"); customer.setId("1"); customer.setRegistrationDate(Instant.now()); // Put item with values for all attributes. customerDynamoDbTable.putItem(customer); // Create a Customer instance with the same id value, but a different name value. // Do not set the 'registrationDate' attribute. Customer custForUpdate = new Customer(); custForUpdate.setCustName("NewName"); custForUpdate.setEmail("email"); custForUpdate.setId("1"); // Update item without setting the registrationDate attribute. customerDynamoDbTable.updateItem(b -> b .item(custForUpdate) .ignoreNulls(Boolean.TRUE)); Customer updatedWithNullsIgnored = customerDynamoDbTable.getItem(customer); // registrationDate value is unchanged. logger.info(updatedWithNullsIgnored.toString()); customerDynamoDbTable.updateItem(custForUpdate); Customer updatedWithNulls = customerDynamoDbTable.getItem(customer); // registrationDate value is null because ignoreNulls() was not used. logger.info(updatedWithNulls.toString()); } } // Logged lines. Customer [id=1, custName=NewName, email=email, registrationDate=2023-04-05T16:32:32.056Z] Customer [id=1, custName=NewName, email=email, registrationDate=null]

DynamoDB 향상된 클라이언트의 기본 메서드

향상된 클라이언트의 기본 메서드는 이름이 붙은 DynamoDB 서비스 작업에 매핑합니다. 다음 예제는 각 방법의 가장 간단한 변형을 보여줍니다. 향상된 요청 개체를 전달하여 각 메서드를 사용자 지정할 수 있습니다. 향상된 요청 객체는 표준 DynamoDB 클라이언트에서 사용할 수 있는 대부분의 기능을 제공합니다. 이는 AWS SDK for Java 2.x API 참조에 완전히 문서화되어 있습니다.

이 예제에서는 이전에 표시된 Customer 클래스을 사용합니다.

// CreateTable customerTable.createTable(); // GetItem Customer customer = customerTable.getItem(Key.builder().partitionValue("a123").build()); // UpdateItem Customer updatedCustomer = customerTable.updateItem(customer); // PutItem customerTable.putItem(customer); // DeleteItem Customer deletedCustomer = customerTable.deleteItem(Key.builder().partitionValue("a123").sortValue(456).build()); // Query PageIterable<Customer> customers = customerTable.query(keyEqualTo(k -> k.partitionValue("a123"))); // Scan PageIterable<Customer> customers = customerTable.scan(); // BatchGetItem BatchGetResultPageIterable batchResults = enhancedClient.batchGetItem(r -> r.addReadBatch(ReadBatch.builder(Customer.class) .mappedTableResource(customerTable) .addGetItem(key1) .addGetItem(key2) .addGetItem(key3) .build())); // BatchWriteItem batchResults = enhancedClient.batchWriteItem(r -> r.addWriteBatch(WriteBatch.builder(Customer.class) .mappedTableResource(customerTable) .addPutItem(customer) .addDeleteItem(key1) .addDeleteItem(key1) .build())); // TransactGetItems transactResults = enhancedClient.transactGetItems(r -> r.addGetItem(customerTable, key1) .addGetItem(customerTable, key2)); // TransactWriteItems enhancedClient.transactWriteItems(r -> r.addConditionCheck(customerTable, i -> i.key(orderKey) .conditionExpression(conditionExpression)) .addUpdateItem(customerTable, customer) .addDeleteItem(customerTable, key));

DynamoDB 향상된 클라이언트와 표준 DynamoDB 클라이언트 비교

표준고급 DynamoDB 클라이언트 API를 모두 사용하면 DynamoDB 테이블을 사용하여 CRUD(생성, 읽기, 업데이트 및 삭제) 데이터 수준 작업을 수행할 수 있습니다. 클라이언트 API 간의 차이는 이를 수행하는 방법에 있습니다. 표준 클라이언트를 사용하면 저수준 데이터 속성으로 직접 작업할 수 있습니다. 향상된 클라이언트 API는 친숙한 Java 클래스를 사용하고 이면의 하위 수준 API에 매핑됩니다.

두 클라이언트 API 모두 데이터 수준 작업을 지원하지만 표준 DynamoDB 클라이언트는 리소스 수준 작업도 지원합니다. 리소스 수준 작업은 백업 생성, 테이블 나열, 테이블 업데이트와 같은 데이터베이스를 관리합니다. 향상된 클라이언트 API는 테이블 생성, 설명, 삭제와 같은 엄선된 리소스 수준 작업을 지원합니다.

두 클라이언트 API에서 사용하는 다양한 접근 방식을 설명하기 위해 다음 코드 예제는 표준 클라이언트와 향상된 클라이언트를 사용하여 동일한 ProductCatalog 테이블을 생성하는 방법을 보여줍니다.

비교: 표준 DynamoDB 클라이언트를 사용하여 테이블 생성

DependencyFactory.dynamoDbClient().createTable(builder -> builder .tableName(TABLE_NAME) .attributeDefinitions( b -> b.attributeName("id").attributeType(ScalarAttributeType.N), b -> b.attributeName("title").attributeType(ScalarAttributeType.S), b -> b.attributeName("isbn").attributeType(ScalarAttributeType.S) ) .keySchema( builder1 -> builder1.attributeName("id").keyType(KeyType.HASH), builder2 -> builder2.attributeName("title").keyType(KeyType.RANGE) ) .globalSecondaryIndexes(builder3 -> builder3 .indexName("products_by_isbn") .keySchema(builder2 -> builder2 .attributeName("isbn").keyType(KeyType.HASH)) .projection(builder2 -> builder2 .projectionType(ProjectionType.INCLUDE) .nonKeyAttributes("price", "authors")) .provisionedThroughput(builder4 -> builder4 .writeCapacityUnits(5L).readCapacityUnits(5L)) ) .provisionedThroughput(builder1 -> builder1 .readCapacityUnits(5L).writeCapacityUnits(5L)) );

비교: DynamoDB 향상된 클라이언트를 사용하여 테이블 생성

DynamoDbEnhancedClient enhancedClient = DependencyFactory.enhancedClient(); productCatalog = enhancedClient.table(TABLE_NAME, TableSchema.fromImmutableClass(ProductCatalog.class)); productCatalog.createTable(b -> b .provisionedThroughput(b1 -> b1.readCapacityUnits(5L).writeCapacityUnits(5L)) .globalSecondaryIndices(b2 -> b2.indexName("products_by_isbn") .projection(b4 -> b4 .projectionType(ProjectionType.INCLUDE) .nonKeyAttributes("price", "authors")) .provisionedThroughput(b3 -> b3.writeCapacityUnits(5L).readCapacityUnits(5L)) ) );

향상된 클라이언트는 다음과 같은 주석이 달린 데이터 클래스를 사용합니다. DynamoDB 향상된 클라이언트는 Java 데이터 형식을 DynamoDB 데이터 형식에 매핑하므로 코드가 복잡하지 않고 따라하기 쉽습니다. ProductCatalog는 DynamoDB 향상된 클라이언트에서 변경할 수 없는 클래스를 사용하는 예입니다. 매핑된 데이터 클래스에 변경할 수 없는 클래스를 사용하는 방법은 이 항목의 뒷부분에서 설명합니다.

package org.example.tests.model; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; import java.math.BigDecimal; import java.util.Objects; import java.util.Set; @DynamoDbImmutable(builder = ProductCatalog.Builder.class) public class ProductCatalog implements Comparable<ProductCatalog> { private Integer id; private String title; private String isbn; private Set<String> authors; private BigDecimal price; private ProductCatalog(Builder builder){ this.authors = builder.authors; this.id = builder.id; this.isbn = builder.isbn; this.price = builder.price; this.title = builder.title; } public static Builder builder(){ return new Builder(); } @DynamoDbPartitionKey public Integer id() { return id; } @DynamoDbSortKey public String title() { return title; } @DynamoDbSecondaryPartitionKey(indexNames = "products_by_isbn") public String isbn() { return isbn; } public Set<String> authors() { return authors; } public BigDecimal price() { return price; } public static final class Builder { private Integer id; private String title; private String isbn; private Set<String> authors; private BigDecimal price; private Builder(){} public Builder id(Integer id) { this.id = id; return this; } public Builder title(String title) { this.title = title; return this; } public Builder isbn(String ISBN) { this.isbn = ISBN; return this; } public Builder authors(Set<String> authors) { this.authors = authors; return this; } public Builder price(BigDecimal price) { this.price = price; return this; } public ProductCatalog build() { return new ProductCatalog(this); } } @Override public String toString() { final StringBuffer sb = new StringBuffer("ProductCatalog{"); sb.append("id=").append(id); sb.append(", title='").append(title).append('\''); sb.append(", isbn='").append(isbn).append('\''); sb.append(", authors=").append(authors); sb.append(", price=").append(price); sb.append('}'); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProductCatalog that = (ProductCatalog) o; return id.equals(that.id) && title.equals(that.title) && Objects.equals(isbn, that.isbn) && Objects.equals(authors, that.authors) && Objects.equals(price, that.price); } @Override public int hashCode() { return Objects.hash(id, title, isbn, authors, price); } @Override @DynamoDbIgnore public int compareTo(ProductCatalog other) { if (this.id.compareTo(other.id) != 0){ return this.id.compareTo(other.id); } else { return this.title.compareTo(other.title); } } }

다음 두 개의 일괄 쓰기 코드 예제는 향상된 클라이언트와 달리 표준 클라이언트를 사용할 때 장황하고 형식 안전성이 부족하다는 것을 보여줍니다.

public static void batchWriteStandard(DynamoDbClient dynamoDbClient, String tableName) { Map<String, AttributeValue> catalogItem = Map.of( "authors", AttributeValue.builder().ss("a", "b").build(), "id", AttributeValue.builder().n("1").build(), "isbn", AttributeValue.builder().s("1-565-85698").build(), "title", AttributeValue.builder().s("Title 1").build(), "price", AttributeValue.builder().n("52.13").build()); Map<String, AttributeValue> catalogItem2 = Map.of( "authors", AttributeValue.builder().ss("a", "b", "c").build(), "id", AttributeValue.builder().n("2").build(), "isbn", AttributeValue.builder().s("1-208-98073").build(), "title", AttributeValue.builder().s("Title 2").build(), "price", AttributeValue.builder().n("21.99").build()); Map<String, AttributeValue> catalogItem3 = Map.of( "authors", AttributeValue.builder().ss("g", "k", "c").build(), "id", AttributeValue.builder().n("3").build(), "isbn", AttributeValue.builder().s("7-236-98618").build(), "title", AttributeValue.builder().s("Title 3").build(), "price", AttributeValue.builder().n("42.00").build()); Set<WriteRequest> writeRequests = Set.of( WriteRequest.builder().putRequest(b -> b.item(catalogItem)).build(), WriteRequest.builder().putRequest(b -> b.item(catalogItem2)).build(), WriteRequest.builder().putRequest(b -> b.item(catalogItem3)).build()); Map<String, Set<WriteRequest>> productCatalogItems = Map.of( "ProductCatalog", writeRequests); BatchWriteItemResponse response = dynamoDbClient.batchWriteItem(b -> b.requestItems(productCatalogItems)); logger.info("Unprocessed items: " + response.unprocessedItems().size()); }
public static void batchWriteEnhanced(DynamoDbTable<ProductCatalog> productCatalog) { ProductCatalog prod = ProductCatalog.builder() .id(1) .isbn("1-565-85698") .authors(new HashSet<>(Arrays.asList("a", "b"))) .price(BigDecimal.valueOf(52.13)) .title("Title 1") .build(); ProductCatalog prod2 = ProductCatalog.builder() .id(2) .isbn("1-208-98073") .authors(new HashSet<>(Arrays.asList("a", "b", "c"))) .price(BigDecimal.valueOf(21.99)) .title("Title 2") .build(); ProductCatalog prod3 = ProductCatalog.builder() .id(3) .isbn("7-236-98618") .authors(new HashSet<>(Arrays.asList("g", "k", "c"))) .price(BigDecimal.valueOf(42.00)) .title("Title 3") .build(); BatchWriteResult batchWriteResult = DependencyFactory.enhancedClient() .batchWriteItem(b -> b.writeBatches( WriteBatch.builder(ProductCatalog.class) .mappedTableResource(productCatalog) .addPutItem(prod).addPutItem(prod2).addPutItem(prod3) .build() )); logger.info("Unprocessed items: " + batchWriteResult.unprocessedPutItemsForTable(productCatalog).size()); }