

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

# 작업 DynamoDB
<a name="examples-dynamodb"></a>

[DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html)는 완전관리형 NoSQL 데이터베이스 서비스로, 원활한 확장성과 함께 빠르고 예측 가능한 성능을 제공합니다. 이 섹션에서는 AWS SDK for Java 2.x를 사용하여 DynamoDB로 작업하는 방법을 보여줍니다.

## DynamoDB 클라이언트 선택
<a name="ddb-clients-overview"></a>

SDK는 DynamoDB를 사용한 작업을 위한 2가지 주요 접근 방식을 제공합니다.

하위 수준 클라이언트(`DynamoDbClient`)  
요청 및 응답을 완전히 제어할 수 있는 DynamoDB 작업에 대한 직접 액세스를 제공합니다. 세분화된 제어가 필요하거나 동적 스키마로 작업하는 경우 이 클라이언트를 사용합니다.

향상된 클라이언트(`DynamoDbEnhancedClient`)  
Java 객체와 DynamoDB 항목 간의 자동 매핑을 통해 객체 중심 프로그래밍을 제공합니다. 또한 고정된 스키마를 따르지 않는 JSON과 유사한 데이터 작업을 위한 문서 중심 기능을 제공합니다. 잘 정의된 데이터 모델 또는 문서 유형 데이터로 작업할 때 이 클라이언트를 사용합니다.

## DynamoDB 클라이언트 구성
<a name="ddb-configuration-setup"></a>

DynamoDB로 작업하기 전에 최적의 성능과 신뢰성을 위해 클라이언트를 구성합니다.

### DynamoDB 재시도 동작 이해
<a name="ddb-retry-behavior"></a>

DynamoDB 클라이언트는 다른 AWS 서비스 클라이언트보다 많은 기본 최대 재시도 횟수인 8을 사용합니다. 이렇게 재시도 횟수가 많으면 DynamoDB의 분산 특성 및 임시 용량 제한을 처리하는 데 도움이 됩니다. 재시도 전략에 대한 자세한 내용은 [에서 재시도 동작 구성 AWS SDK for Java 2.x](retry-strategy.md) 섹션을 참조하세요.

### 계정 기반 엔드포인트로 성능 최적화
<a name="ddb-account-based-endpoints-v2"></a>

DynamoDB는 [AWS 계정 ID를 사용하여 요청 라우팅을 간소화하여 성능을 개선하는 계정 기반 엔드포인트](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.SDKOverview.html#Programming.SDKs.endpoints)를 제공합니다. AWS 

이 기능을 사용하려면 버전 2.28.4 이상의 AWS SDK for Java 2.x가 필요합니다. [Maven 중앙 리포지토리](https://central.sonatype.com/artifact/software.amazon.awssdk/bom/versions)에서 최신 버전을 확인해 보세요. 지원되는 SDK 버전은 자동으로 새 엔드포인트를 사용합니다.

계정 기반 라우팅을 옵트아웃하려면 다음 옵션 중 하나를 선택합니다.
+ `AccountIdEndpointMode`를 `DISABLED`로 설정하여 DynamoDB 서비스 클라이언트를 구성합니다.
+ 환경 변수를 설정합니다.
+ JVM 시스템 속성을 설정합니다.
+ 공유 AWS 구성 파일 설정을 업데이트합니다.

다음 예제에서는 DynamoDB 서비스 클라이언트를 구성하여 계정 기반 라우팅을 사용 해제하는 방법을 보여줍니다.

```
DynamoDbClient.builder()
                 .accountIdEndpointMode(AccountIdEndpointMode.DISABLED)
                 .build();
```

다른 구성 옵션에 대한 자세한 내용은 SDK 및 도구 참조 안내서의 [계정 기반 엔드포인트](https://docs.aws.amazon.com/sdkref/latest/guide/feature-account-endpoints.html)를 참조하세요. AWS SDKs 

## 이 주제에서 다루는 내용
<a name="ddb-topics-overview"></a>

다음 섹션에서는 DynamoDB로 작업하는 방법을 보여줍니다.
+ [DynamoDB의 테이블 다루기](examples-dynamodb-tables.md) - 테이블을 만들고, 설명, 업데이트 및 삭제
+ [의 항목 작업 DynamoDB](examples-dynamodb-items.md) - 개별 항목 추가, 검색 및 업데이트
+ [를 사용하여 Java 객체를 DynamoDB 항목에 매핑 AWS SDK for Java 2.x](dynamodb-enhanced-client.md) - 향상된 클라이언트에서 객체 매핑 및 문서 중심 데이터 사용

추가 DynamoDB 코드 예제는 [코드 예제 라이브러리의 DynamoDB ](java_dynamodb_code_examples.md) AWS 코드 예제를 참조하세요.

# DynamoDB의 테이블 다루기
<a name="examples-dynamodb-tables"></a>

테이블은 DynamoDB 데이터베이스에 있는 모든 항목의 컨테이너입니다. DynamoDB에 데이터를 추가하거나 삭제하기 전에 먼저 테이블을 만들어야 합니다.

각 테이블마다 다음을 정의해야 합니다.
+ 계정 및 리전에 고유한 테이블 *이름*입니다.
+ 모든 값이 고유한 *기본 키*. 테이블의 두 항목에 동일한 기본 키 값을 지정할 수 없습니다.

  기본 키는 단일 파티션(HASH) 키로 이루어진 *단순형*이거나, 파티션과 정렬(RANGE) 키로 이루어진 *복합형*일 수 있습니다.

  각 키 값에는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html) 클래스에 따라 열거되는 관련 *데이터 유형*이 있습니다. 키 값은 이진(B), 숫자(N) 또는 문자열(S)일 수 있습니다. 자세한 정보는 Amazon DynamoDB 개발자 안내서의 [명명 규칙과 데이터 유형](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)을 참조하세요.
+  *프로비저닝된 처리량*은 테이블에 예약된 읽기/쓰기 용량 단위 수를 정의하는 값입니다.
**참고**  
 [Amazon DynamoDB 요금](https://aws.amazon.com/dynamodb/pricing/)은 테이블에 대해 설정하는 프로비저닝된 처리량 값을 기준으로 하므로 테이블에 필요하다고 생각되는 만큼의 용량만 예약하세요.

  언제라도 테이블의 프로비저닝된 처리량을 수정할 수 있으므로 변경이 필요할 경우 용량을 조정할 수 있습니다.

## 테이블 생성
<a name="dynamodb-create-table"></a>

`DynamoDbClient’s` `createTable` 메서드를 사용하여 새 DynamoDB 테이블을 만듭니다. 테이블 속성과 테이블 스키마를 구성해야 하며, 이 두 가지 요소 모두 테이블의 기본 키를 식별하는 데 사용됩니다. 또한 프로비저닝된 초기 처리량 값과 테이블 이름도 지정해야 합니다.

**참고**  
선택한 이름의 테이블이 이미 있는 경우 `[DynamoDbException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html)`이 발생합니다.

### 단순형 기본 키를 사용하여 테이블 생성
<a name="dynamodb-create-table-simple"></a>

이 코드는 테이블의 단순 프라이머리 키라는 속성 하나를 포함하는 테이블을 만듭니다. 이 예제에서는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html)에 `[AttributeDefinition](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeDefinition.html)` 및 `[KeySchemaElement](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/KeySchemaElement.html)` 객체를 사용합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;
```

 ** 코드** 

```
    public static String createTable(DynamoDbClient ddb, String tableName, String key) {

        DynamoDbWaiter dbWaiter = ddb.waiter();
        CreateTableRequest request = CreateTableRequest.builder()
                .attributeDefinitions(AttributeDefinition.builder()
                        .attributeName(key)
                        .attributeType(ScalarAttributeType.S)
                        .build())
                .keySchema(KeySchemaElement.builder()
                        .attributeName(key)
                        .keyType(KeyType.HASH)
                        .build())
                .provisionedThroughput(ProvisionedThroughput.builder()
                        .readCapacityUnits(new Long(10))
                        .writeCapacityUnits(new Long(10))
                        .build())
                .tableName(tableName)
                .build();

        String newTable ="";
        try {
            CreateTableResponse response = ddb.createTable(request);
            DescribeTableRequest tableRequest = DescribeTableRequest.builder()
                    .tableName(tableName)
                    .build();

            // Wait until the Amazon DynamoDB table is created
            WaiterResponse<DescribeTableResponse> waiterResponse =  dbWaiter.waitUntilTableExists(tableRequest);
            waiterResponse.matched().response().ifPresent(System.out::println);

            newTable = response.tableDescription().tableName();
            return newTable;

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
       return "";
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTable.java)를 참조하세요.

### 복합형 기본 키를 사용하여 테이블 생성
<a name="dynamodb-create-table-composite"></a>

다음 예제는 두 가지 속성이 있는 테이블을 만듭니다. 두 속성 모두 복합 기본 키에 사용됩니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
```

 ** 코드** 

```
    public static String createTableComKey(DynamoDbClient ddb, String tableName) {
        CreateTableRequest request = CreateTableRequest.builder()
                .attributeDefinitions(
                        AttributeDefinition.builder()
                                .attributeName("Language")
                                .attributeType(ScalarAttributeType.S)
                                .build(),
                        AttributeDefinition.builder()
                                .attributeName("Greeting")
                                .attributeType(ScalarAttributeType.S)
                                .build())
                .keySchema(
                        KeySchemaElement.builder()
                                .attributeName("Language")
                                .keyType(KeyType.HASH)
                                .build(),
                        KeySchemaElement.builder()
                                .attributeName("Greeting")
                                .keyType(KeyType.RANGE)
                                .build())
                .provisionedThroughput(
                        ProvisionedThroughput.builder()
                                .readCapacityUnits(new Long(10))
                                .writeCapacityUnits(new Long(10)).build())
                .tableName(tableName)
                .build();

       String tableId = "";

       try {
            CreateTableResponse result = ddb.createTable(request);
            tableId = result.tableDescription().tableId();
            return tableId;
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
       return "";
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTableCompositeKey.java)를 참조하세요.

## 테이블 나열
<a name="dynamodb-list-tables"></a>

`DynamoDbClient’s` `listTables` 메서드를 호출하여 특정 리전의 테이블을 나열할 수 있습니다.

**참고**  
계정 및 리전에 대해 이름이 지정된 테이블이 없으면 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)이 발생합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse;
import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import java.util.List;
```

 ** 코드** 

```
    public static void listAllTables(DynamoDbClient ddb){

        boolean moreTables = true;
        String lastName = null;

        while(moreTables) {
            try {
                ListTablesResponse response = null;
                if (lastName == null) {
                    ListTablesRequest request = ListTablesRequest.builder().build();
                    response = ddb.listTables(request);
                } else {
                    ListTablesRequest request = ListTablesRequest.builder()
                            .exclusiveStartTableName(lastName).build();
                    response = ddb.listTables(request);
                }

                List<String> tableNames = response.tableNames();

                if (tableNames.size() > 0) {
                    for (String curName : tableNames) {
                        System.out.format("* %s\n", curName);
                    }
                } else {
                    System.out.println("No tables found!");
                    System.exit(0);
                }

                lastName = response.lastEvaluatedTableName();
                if (lastName == null) {
                    moreTables = false;
                }
            } catch (DynamoDbException e) {
                System.err.println(e.getMessage());
                System.exit(1);
            }
        }
        System.out.println("\nDone!");
    }
```

기본적으로 호출당 최대 100개의 테이블이 반환됩니다. 반환된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html) 객체에 `lastEvaluatedTableName`을 사용하여 마지막으로 평가된 테이블을 가져올 수 있습니다. 이 값을 사용하여 이전 목록의 마지막으로 반환된 값 다음에 이어지는 목록을 시작할 수 있습니다.

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/ListTables.java)를 참조하세요.

## 테이블 설명(테이블에 대한 정보 가져오기)
<a name="dynamodb-describe-table"></a>

테이블에 대한 정보를 가져오려면 `DynamoDbClient’s` `describeTable` 메서드를 사용합니다.

**참고**  
계정 및 리전에 대해 이름이 지정된 테이블이 없으면 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)이 발생합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughputDescription;
import software.amazon.awssdk.services.dynamodb.model.TableDescription;
import java.util.List;
```

 ** 코드** 

```
    public static void describeDymamoDBTable(DynamoDbClient ddb,String tableName ) {

        DescribeTableRequest request = DescribeTableRequest.builder()
                .tableName(tableName)
                .build();

        try {
            TableDescription tableInfo =
                    ddb.describeTable(request).table();

            if (tableInfo != null) {
                System.out.format("Table name  : %s\n",
                        tableInfo.tableName());
                System.out.format("Table ARN   : %s\n",
                        tableInfo.tableArn());
                System.out.format("Status      : %s\n",
                        tableInfo.tableStatus());
                System.out.format("Item count  : %d\n",
                        tableInfo.itemCount().longValue());
                System.out.format("Size (bytes): %d\n",
                        tableInfo.tableSizeBytes().longValue());

                ProvisionedThroughputDescription throughputInfo =
                        tableInfo.provisionedThroughput();
                System.out.println("Throughput");
                System.out.format("  Read Capacity : %d\n",
                        throughputInfo.readCapacityUnits().longValue());
                System.out.format("  Write Capacity: %d\n",
                        throughputInfo.writeCapacityUnits().longValue());

                List<AttributeDefinition> attributes =
                        tableInfo.attributeDefinitions();
                System.out.println("Attributes");

                for (AttributeDefinition a : attributes) {
                    System.out.format("  %s (%s)\n",
                            a.attributeName(), a.attributeType());
                }
            }
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        System.out.println("\nDone!");
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DescribeTable.java)를 참조하세요.

## 테이블 수정(업데이트)
<a name="dynamodb-update-table"></a>

언제라도 `DynamoDbClient’s` `updateTable` 메서드를 호출하여 테이블의 프로비저닝된 처리량 값을 수정할 수 있습니다.

**참고**  
계정 및 리전에 대해 이름이 지정된 테이블이 없으면 [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)이 발생합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.UpdateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
```

 ** 코드** 

```
    public static void updateDynamoDBTable(DynamoDbClient ddb,
                                           String tableName,
                                           Long readCapacity,
                                           Long writeCapacity) {

        System.out.format(
                "Updating %s with new provisioned throughput values\n",
                tableName);
        System.out.format("Read capacity : %d\n", readCapacity);
        System.out.format("Write capacity : %d\n", writeCapacity);

        ProvisionedThroughput tableThroughput = ProvisionedThroughput.builder()
                .readCapacityUnits(readCapacity)
                .writeCapacityUnits(writeCapacity)
                .build();

        UpdateTableRequest request = UpdateTableRequest.builder()
                .provisionedThroughput(tableThroughput)
                .tableName(tableName)
                .build();

        try {
            ddb.updateTable(request);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }

        System.out.println("Done!");
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateTable.java)를 참조하세요.

## 테이블 삭제
<a name="dynamodb-delete-table"></a>

테이블을 삭제하려면 `DynamoDbClient’s` `deleteTable` 메서드를 호출하고 테이블 이름을 제공하세요.

**참고**  
계정 및 리전에 대해 이름이 지정된 테이블이 없으면 [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)이 발생합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
```

 ** 코드** 

```
    public static void deleteDynamoDBTable(DynamoDbClient ddb, String tableName) {

        DeleteTableRequest request = DeleteTableRequest.builder()
                .tableName(tableName)
                .build();

        try {
            ddb.deleteTable(request);

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        System.out.println(tableName +" was successfully deleted!");
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteTable.java)를 참조하세요.

## 추가 정보
<a name="more-information"></a>
+  Amazon DynamoDB 개발자 안내서의 [테이블 작업 지침](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html)
+  Amazon DynamoDB 개발자 안내서의 [DynamoDB의 테이블 다루기](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html)

# 의 항목 작업 DynamoDB
<a name="examples-dynamodb-items"></a>

에서 DynamoDB항목은 *속성* 모음으로, 각 속성에는 *이름과* *값이* 있습니다. 속성 값은 스칼라, 세트 또는 문서 유형일 수 있습니다. 자세한 정보는 Amazon DynamoDB 개발자 안내서의 [명명 규칙과 데이터 유형](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)을 참조하세요.

## 테이블에서 항목 검색(가져오기)
<a name="dynamodb-get-item"></a>

DynamoDbClient의 `getItem` 메서드를 호출하여 테이블 이름과 원하는 항목의 기본 키 값이 포함된 [GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html) 객체를 이 메서드에 전달합니다. [GetItemResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemResponse.html) 객체와 해당되는 모든 속성을 반환합니다. 특정 속성을 검색하기 위해 `GetItemRequest`에서 하나 이상의 [프로젝션 표현식](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)을 지정할 수 있습니다.

반환된 `GetItemResponse` 객체의 `item()` 메서드를 사용하여 항목과 연결된 키(문자열) 및 값([AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html)) 쌍의 [맵](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html)을 가져올 수 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
```

 **코드** 

```
    public static void getDynamoDBItem(DynamoDbClient ddb,String tableName,String key,String keyVal ) {

        HashMap<String,AttributeValue> keyToGet = new HashMap<String,AttributeValue>();

        keyToGet.put(key, AttributeValue.builder()
                .s(keyVal).build());

        GetItemRequest request = GetItemRequest.builder()
                .key(keyToGet)
                .tableName(tableName)
                .build();

        try {
            Map<String,AttributeValue> returnedItem = ddb.getItem(request).item();

            if (returnedItem != null) {
                Set<String> keys = returnedItem.keySet();
                System.out.println("Amazon DynamoDB table attributes: \n");

                for (String key1 : keys) {
                    System.out.format("%s: %s\n", key1, returnedItem.get(key1).toString());
                }
            } else {
                System.out.format("No item found with the key %s!\n", key);
            }
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/GetItem.java)를 참조하세요.

## 비동기 클라이언트를 사용하여 테이블에서 항목 검색(가져오기)
<a name="id1ddb"></a>

DynamoDbAsyncClient의 `getItem` 메서드를 호출하여 테이블 이름과 원하는 항목의 기본 키 값이 포함된 [GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html) 객체를 이 메서드에 전달합니다.

해당 항목에 대한 모든 속성이 포함된 [Collection](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Collection.html) 인스턴스를 반환할 수 있습니다(다음 예제 참조).

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
```

 **코드** 

```
    public static void getItem(DynamoDbAsyncClient client, String tableName, String key,  String keyVal) {

        HashMap<String, AttributeValue> keyToGet =
                new HashMap<String, AttributeValue>();

        keyToGet.put(key, AttributeValue.builder()
                .s(keyVal).build());

        try {

            // Create a GetItemRequest instance
            GetItemRequest request = GetItemRequest.builder()
                    .key(keyToGet)
                    .tableName(tableName)
                    .build();

            // Invoke the DynamoDbAsyncClient object's getItem
            java.util.Collection<AttributeValue> returnedItem = client.getItem(request).join().item().values();

            // Convert Set to Map
            Map<String, AttributeValue> map = returnedItem.stream().collect(Collectors.toMap(AttributeValue::s, s->s));
            Set<String> keys = map.keySet();
            for (String sinKey : keys) {
                System.out.format("%s: %s\n", sinKey, map.get(sinKey).toString());
            }

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodbasync/src/main/java/com/example/dynamodbasync/DynamoDBAsyncGetItem.java)를 참조하세요.

## 테이블에 새 항목 추가
<a name="dynamodb-add-item"></a>

항목의 속성을 나타내는 키-값 페어의 [맵](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html)을 생성합니다. 여기에는 테이블의 기본 키 필드 값이 포함되어야 합니다. 기본 키로 식별되는 항목이 이미 존재하면 필드가 요청에 따라 *업데이트됩니다*.

**참고**  
계정 및 리전에 대해 이름이 지정된 테이블이 없으면 [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)이 발생합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import java.util.HashMap;
```

 **코드** 

```
    public static void putItemInTable(DynamoDbClient ddb,
                                      String tableName,
                                      String key,
                                      String keyVal,
                                      String albumTitle,
                                      String albumTitleValue,
                                      String awards,
                                      String awardVal,
                                      String songTitle,
                                      String songTitleVal){

        HashMap<String,AttributeValue> itemValues = new HashMap<String,AttributeValue>();

        // Add all content to the table
        itemValues.put(key, AttributeValue.builder().s(keyVal).build());
        itemValues.put(songTitle, AttributeValue.builder().s(songTitleVal).build());
        itemValues.put(albumTitle, AttributeValue.builder().s(albumTitleValue).build());
        itemValues.put(awards, AttributeValue.builder().s(awardVal).build());

        PutItemRequest request = PutItemRequest.builder()
                .tableName(tableName)
                .item(itemValues)
                .build();

        try {
            ddb.putItem(request);
            System.out.println(tableName +" was successfully updated");

        } catch (ResourceNotFoundException e) {
            System.err.format("Error: The Amazon DynamoDB table \"%s\" can't be found.\n", tableName);
            System.err.println("Be sure that it exists and that you've typed its name correctly!");
            System.exit(1);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/PutItem.java)를 참조하세요.

## 테이블의 기존 항목 업데이트
<a name="dynamodb-update-item"></a>

DynamoDbClient의 `updateItem` 메서드를 사용하여 테이블 이름, 기본 키 값 및 업데이트할 필드 맵을 지정함으로써 테이블에 이미 존재하는 항목의 속성을 업데이트할 수 있습니다.

**참고**  
해당 계정 및 리전에 대해 이름이 지정된 테이블이 없거나 전달한 기본 키로 식별되는 항목이 없으면 [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)이 발생합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.AttributeAction;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.AttributeValueUpdate;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import java.util.HashMap;
```

 **코드** 

```
    public static void updateTableItem(DynamoDbClient ddb,
                                       String tableName,
                                       String key,
                                       String keyVal,
                                       String name,
                                       String updateVal){

        HashMap<String,AttributeValue> itemKey = new HashMap<String,AttributeValue>();

        itemKey.put(key, AttributeValue.builder().s(keyVal).build());

        HashMap<String,AttributeValueUpdate> updatedValues =
                new HashMap<String,AttributeValueUpdate>();

        // Update the column specified by name with updatedVal
        updatedValues.put(name, AttributeValueUpdate.builder()
                .value(AttributeValue.builder().s(updateVal).build())
                .action(AttributeAction.PUT)
                .build());

        UpdateItemRequest request = UpdateItemRequest.builder()
                .tableName(tableName)
                .key(itemKey)
                .attributeUpdates(updatedValues)
                .build();

        try {
            ddb.updateItem(request);
        } catch (ResourceNotFoundException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }

        System.out.println("Done!");
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateItem.java)를 참조하세요.

## 테이블의 기존 항목 삭제
<a name="dynamodb-delete-item"></a>

DynamoDbClient의 `deleteItem` 메서드를 사용하고 기본 키 값 및 테이블 이름을 지정하여 테이블에 있는 항목을 삭제할 수 있습니다.

**참고**  
해당 계정 및 리전에 대해 이름이 지정된 테이블이 없거나 전달한 기본 키로 식별되는 항목이 없으면 [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)이 발생합니다.

 **가져오기** 

```
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.DeleteItemRequest;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import java.util.HashMap;
```

 **코드** 

```
  public static void deleteDynamoDBItem(DynamoDbClient ddb, String tableName, String key, String keyVal) {

        HashMap<String,AttributeValue> keyToGet =
                new HashMap<String,AttributeValue>();

        keyToGet.put(key, AttributeValue.builder()
                .s(keyVal)
                .build());

        DeleteItemRequest deleteReq = DeleteItemRequest.builder()
                .tableName(tableName)
                .key(keyToGet)
                .build();

        try {
            ddb.deleteItem(deleteReq);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteItem.java)를 참조하세요.

## 추가 정보
<a name="more-information"></a>
+  Amazon DynamoDB 개발자 안내서의 [항목 작업 지침](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/best-practices.html)
+  Amazon DynamoDB 개발자 안내서의 [에서 항목 작업 DynamoDB](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/WorkingWithItems.html) 

# 를 사용하여 Java 객체를 DynamoDB 항목에 매핑 AWS SDK for Java 2.x
<a name="dynamodb-enhanced-client"></a>

[DynamoDB 향상된 클라이언트 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html)는 Java v1.x용 SDK `DynamoDBMapper` 클래스의 후속 클래스인 상위 수준 라이브러리입니다. 클라이언트 측 클래스를 DynamoDB 테이블에 매핑하는 간단한 방법을 제공합니다. 코드에서 테이블과 해당 데이터 클래스 간의 관계를 정의합니다. 이러한 관계를 정의한 다음 DynamoDB의 테이블 또는 항목에 대해 다양한 생성, 읽기, 업데이트 또는 삭제(CRUD) 작업을 직관적으로 수행할 수 있습니다.

DynamoDB 향상된 클라이언트 API에는 정의된 스키마를 따르지 않는 문서 유형 항목을 처리할 수 있는 [향상된 클라이언트 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html)도 포함되어 있습니다.

**Topics**
+ [DynamoDB 향상된 클라이언트 API를 사용하여 시작하기](ddb-en-client-getting-started.md)
+ [DynamoDB 향상된 클라이언트 API의 기본 사항 알아보기](ddb-en-client-use.md)
+ [고급 매핑 기능 사용](ddb-en-client-adv-features.md)
+ [DynamoDB용 향상된 문서 API를 사용하여 JSON 문서로 작업](ddb-en-client-doc-api.md)
+ [확장 프로그램을 사용하여 DynamoDB 향상된 클라이언트 작업 사용자 지정](ddb-en-client-extensions.md)
+ [비동기식으로 DynamoDB 향상된 클라이언트 API 사용](ddb-en-client-async.md)
+ [데이터 클래스 주석](ddb-en-client-anno-index.md)

# DynamoDB 향상된 클라이언트 API를 사용하여 시작하기
<a name="ddb-en-client-getting-started"></a>

다음 자습서에서는 DynamoDB 향상된 클라이언트 API를 사용하는 데 필요한 기본 사항을 소개합니다.

## 종속성 추가
<a name="ddb-en-client-gs-dep"></a>

프로젝트에서 DynamoDB 향상된 클라이언트 API로 작업을 시작하려면 `dynamodb-enhanced` Maven 아티팩트에 종속성을 추가하세요. 방법은 다음 예제와 같습니다.

------
#### [ Maven ]

```
<project>
  <dependencyManagement>
   <dependencies>
      <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>bom</artifactId>
        <version><VERSION></version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
   </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>dynamodb-enhanced</artifactId>
    </dependency>
  </dependencies>
  ...
</project>
```

Maven 중앙 리포지토리에서 [최신 버전](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)을 검색하고 *<VERSION>*을 이 값으로 바꾸세요.

------
#### [ Gradle ]

```
repositories {
    mavenCentral()
}
dependencies {
    implementation(platform("software.amazon.awssdk:bom:<VERSION>"))
    implementation("software.amazon.awssdk:dynamodb-enhanced")
    ...
}
```

Maven 중앙 리포지토리에서 [최신 버전](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)을 검색하고 *<VERSION>*을 이 값으로 바꾸세요.

------

# 데이터 클래스에서 `TableSchema` 생성
<a name="ddb-en-client-gs-tableschema"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)`를 사용하면 향상된 클라이언트가 DynamoDB 속성 값을 클라이언트 측 클래스와 매핑하거나 클라이언트 측 클래스에서 가져올 수 있습니다. 이 자습서에서는 정적 데이터 클래스에서 파생되고 빌더를 사용하여 코드에서 생성되는 `TableSchema`에 대해 알아봅니다.

## 주석이 달린 데이터 클래스 사용하기
<a name="ddb-en-client-gs-tableschema-anno-bean"></a>

Java 2.x용 SDK에는 데이터 클래스와 함께 신속하게 `TableSchema`를 생성하는 데 사용할 수 있는 [일련의 주석](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/package-summary.html)이 포함되어 있습니다.

먼저 [JavaBean 사양](https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf)을 준수하는 데이터 클래스를 만드세요. 이 사양에서는 클래스에 인수가 없는 공용 생성자가 있어야 하고 클래스의 각 속성에 대한 접근자와 설정자가 있어야 합니다. 데이터 클래스가 `DynamoDbBean`임을 나타내는 클래스 수준 주석을 포함하세요. 또한 최소한 접근자 또는 설정자에 기본 키 속성에 대한 `DynamoDbPartitionKey` 주석을 포함해야 합니다.

[속성 수준 주석](ddb-en-client-anno-index.md)을 getter 또는 setter에 적용할 수 있지만 둘 다 적용할 수는 없습니다.

**참고**  
용어 `property`는 일반적으로 JavaBean에서 캡슐화된 값에 사용됩니다. 하지만 이 가이드에서는 DynamoDB에서 사용하는 용어와의 일관성을 위해 용어 `attribute`를 대신 사용합니다.

다음 `Customer` 클래스는 클래스 정의를 DynamoDB 테이블에 연결하는 주석을 보여줍니다.

### `Customer` 클래스
<a name="ddb-en-client-gs-tableschema-anno-bean-cust"></a>

```
package org.example.tests.model;

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.time.Instant;

@DynamoDbBean
public class Customer {

    private String id;
    private String name;
    private String email;
    private Instant regDate;

    @DynamoDbPartitionKey
    public String getId() { return this.id; }

    public void setId(String id) { this.id = id; }

    public String getCustName() { return this.name; }

    public void setCustName(String name) { this.name = name; }

    @DynamoDbSortKey
    public String getEmail() { return this.email; }

    public void setEmail(String email) { this.email = email; }

    public Instant getRegistrationDate() { return this.regDate; }

    public void setRegistrationDate(Instant registrationDate) { this.regDate = registrationDate; }

    @Override
    public String toString() {
        return "Customer [id=" + id + ", name=" + name + ", email=" + email
                + ", regDate=" + regDate + "]";
    }
}
```

주석이 달린 데이터 클래스를 만든 후 다음 코드 조각에 표시된 것처럼 이 클래스를 사용하여 `TableSchema`를 생성합니다.

```
static final TableSchema<Customer> customerTableSchema = TableSchema.fromBean(Customer.class);
```

`TableSchema`는 정적이고 변경할 수 없도록 설계되었습니다. 일반적으로 클래스 로드 시 인스턴스화할 수 있습니다.

정적 `TableSchema.fromBean()` 팩토리 메서드는 bean을 검사하여 DynamoDB 속성과 데이터 클래스 속성 간의 매핑을 생성합니다.

여러 데이터 클래스로 구성된 데이터 모델을 사용하는 예제는 [bean, 맵, 목록 및 세트로 구성된 속성으로 작업](ddb-en-client-adv-features-nested.md) 단원의 `Person` 클래스를 참조하세요.

## 빌더 사용
<a name="ddb-en-client-gs-tableschema-builder"></a>

코드에 테이블 스키마를 정의하면 Bean 인트로스펙션 비용을 건너뛸 수 있습니다. 스키마를 코딩하면 클래스가 JavaBean 이름 지정 표준을 따르지 않아도 되며 주석을 달 필요도 없습니다. 다음 예제는 빌더를 사용하며 주석을 사용하는 `Customer` 클래스 예제와 동일합니다.

```
static final TableSchema<Customer> customerTableSchema =
                TableSchema.builder(Customer.class)
                        .newItemSupplier(Customer::new)
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(Customer::getId)
                                .setter(Customer::setId)
                                .tags(StaticAttributeTags.primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("email")
                                .getter(Customer::getEmail)
                                .setter(Customer::setEmail)
                                .tags(StaticAttributeTags.primarySortKey()))
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(Customer::getCustName)
                                .setter(Customer::setCustName))
                        .addAttribute(Instant.class, a -> a.name("registrationDate")
                                .getter(Customer::getRegistrationDate)
                                .setter(Customer::setRegistrationDate))
                        .build();
```

# 향상된 클라이언트를 만들고 `DynamoDbTable`
<a name="ddb-en-client-getting-started-dynamodbTable"></a>

## 확장 클라이언트 생성
<a name="ddb-en-client-getting-started-dynamodbTable-eclient"></a>

[DynamoDB 향상된 클라이언트](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html) 클래스 또는 이에 상응하는 비동기식 [DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html)는 DynamoDB 향상된 클라이언트 API 사용을 위한 시작점입니다.

향상된 클라이언트에는 작업을 수행하기 위한 표준 `[DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html)`이 필요합니다. API는 `DynamoDbEnhancedClient` 인스턴스를 생성하는 두 가지 방법을 제공합니다. 다음 코드 조각에 표시된 첫 번째 옵션은 구성 설정에서 기본 설정을 선택하여 표준 `DynamoDbClient`을 생성합니다.

```
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.create();
```

기본 표준 클라이언트를 구성하려는 경우 다음 코드 조각과 같이 확장 클라이언트의 빌더 메서드에 제공할 수 있습니다.

```
// Configure an instance of the standard DynamoDbClient.
DynamoDbClient standardClient = DynamoDbClient.builder()
    .region(Region.US_EAST_1)
    .credentialsProvider(ProfileCredentialsProvider.create())
    .build();

// Use the configured standard client with the enhanced client.
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
    .dynamoDbClient(standardClient)
    .build();
```

## `DynamoDbTable` 인스턴스 생성
<a name="ddb-en-client-getting-started-dynamodbTable-table"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)를 `TableSchema`에서 제공하는 매핑 기능을 사용하는 DynamoDB 테이블의 클라이언트 측 표현이라고 생각하면 됩니다. `DynamoDbTable` 클래스는 단일 DynamoDB 테이블과 상호 작용할 수 있는 CRUD 작업을 위한 메서드를 제공합니다.

`DynamoDbTable<T>`는 문서 유형 항목으로 작업할 때 사용자 정의 클래스이든 `EnhancedDocument`이든 관계없이 단일 형식 인수를 사용하는 제네릭 클래스입니다. 이 인수 유형은 사용하는 클래스와 단일 DynamoDB 테이블 간의 관계를 설정합니다.

`DynamoDbEnhancedClient`의 `table()` 팩토리 메서드를 사용하여 다음 코드 조각과 같이 `DynamoDbTable` 인스턴스를 생성합니다.

```
static final DynamoDbTable<Customer> customerTable = 
        enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
```

`DynamoDbTable` 인스턴스는 변경이 불가능하고 애플리케이션 전체에서 사용할 수 있기 때문에 싱글톤에 적합합니다.

이제 코드에 `Customer` 인스턴스로 작업할 수 있는 DynamoDB 테이블의 인메모리 표현이 생겼습니다. 실제 DynamoDB 테이블은 존재할 수도 있고 존재하지 않을 수도 있습니다. 이름이 `Customer`로 지정된 테이블이 이미 있는 경우 해당 테이블에 대해 CRUD 작업을 시작할 수 있습니다. 테이블이 존재하지 않는 경우 다음 단원에서 설명하는 대로 `DynamoDbTable` 인스턴스를 사용하여 테이블을 생성하세요.

# 필요한 경우 DynamoDB 테이블 생성
<a name="ddb-en-client-gs-ddbtable"></a>

`DynamoDbTable` 인스턴스를 생성한 후 이를 사용하여 DynamoDB에서 테이블을 *일회성*으로 생성합니다.

## 테이블 생성 예제 코드
<a name="ddb-en-client-gs-ddbtable-createex"></a>

다음 예제는 `Customer` 데이터 클래스를 기반으로 DynamoDB 테이블을 생성합니다.

이 예제는 클래스 이름과 동일한 이름 `Customer`을 가진 DynamoDB 테이블을 생성하지만 테이블 이름은 다른 이름일 수 있습니다. 테이블 이름을 무엇으로 지정하든 테이블 작업을 하려면 추가 애플리케이션에서 이 이름을 사용해야 합니다. 기본 DynamoDB 테이블을 사용하기 위해 다른 `DynamoDbTable` 객체를 생성할 때마다 이 이름을 `table()` 메서드에 제공하세요.

`createTable` 메서드에 전달된 Java Lambda 파라미터 `builder`를 사용하면 [테이블을 사용자 지정](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html)할 수 있습니다. 이 예시에서는 [프로비저닝된 처리량](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.ProvisionedThroughput.Manual)을 구성합니다. 테이블을 생성할 때 기본 설정을 사용하려면 다음 코드 조각과 같이 빌더를 건너뛰세요.

```
customerTable.createTable();
```

기본 설정을 사용하는 경우 프로비저닝된 처리량 값은 설정되지 않습니다. 대신 테이블의 청구 모드가 [온디맨드](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.OnDemand)로 설정됩니다.

또한 이 예제에서는 응답에서 받은 테이블 이름을 출력하려고 시도하기 전에 `[DynamoDbWaiter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/waiters/DynamoDbWaiter.html)`를 사용합니다. 테이블을 만드는 데 시간이 좀 걸립니다. 따라서 웨이터를 사용하면 테이블을 사용하기 전에 DynamoDB 서비스를 폴링하여 테이블이 존재하는지 확인하는 로직을 작성할 필요가 없습니다.

### 가져오기
<a name="ddb-en-client-gs-ddbtable-imports"></a>

```
import com.example.dynamodb.Customer;
import software.amazon.awssdk.core.internal.waiters.ResponseOrException;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.model.CreateTableEnhancedRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;
```

### 코드
<a name="ddb-en-client-gs-ddbtable-code"></a>

```
 public static void createCustomerTable(DynamoDbTable<Customer> customerTable, DynamoDbClient standardClient) {
     // Create the DynamoDB table using the 'customerTable' DynamoDbTable instance.
     customerTable.createTable(builder -> builder
             .provisionedThroughput(b -> b
                     .readCapacityUnits(10L)
                     .writeCapacityUnits(10L)
                     .build())
     );
     // The DynamoDbClient instance (named 'standardClient') passed to the builder for the DynamoDbWaiter is the same instance
     // that was passed to the builder of the DynamoDbEnhancedClient instance that we created previously.
     // By using the same instance, it ensures that the same Region that was configured on the standard DynamoDbClient 
     // instance is used for other service clients that accept a DynamoDbClient during construction.
     try (DynamoDbWaiter waiter = DynamoDbWaiter.builder().client(standardClient).build()) { // DynamoDbWaiter is Autocloseable
         ResponseOrException<DescribeTableResponse> response = waiter
                 .waitUntilTableExists(builder -> builder.tableName("Customer").build())
                 .matched();
         DescribeTableResponse tableDescription = response.response().orElseThrow(
                 () -> new RuntimeException("Customer table was not created."));
         // The actual error can be inspected in response.exception()
         logger.info("Customer table was created.");
     }
 }
```

**참고**  
데이터 클래스에서 테이블을 생성할 때 DynamoDB 테이블의 속성 이름은 소문자로 시작합니다. 테이블의 속성 이름을 대문자로 시작하려면 [`@DynamoDbAttribute(NAME)` 주석](ddb-en-client-adv-features-inex-attr.md)을 사용하고 원하는 이름을 파라미터로 제공하세요.

# 작업을 수행
<a name="ddb-en-client-gs-use"></a>

테이블이 생성된 후 `DynamoDbTable` 인스턴스를 사용하여 DynamoDB 테이블에 대한 작업을 수행합니다.

다음 예제에서는 `DynamoDbTable<Customer>` 싱글톤이 [`Customer`데이터 클래스](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) 인스턴스와 함께 파라미터로 전달되어 테이블에 새 항목을 추가합니다.

```
    public static void putItemExample(DynamoDbTable<Customer> customerTable, Customer customer){
        logger.info(customer.toString());
        customerTable.putItem(customer);
    }
```

## `Customer` 객체
<a name="perform_ops_create_customer_instatnce"></a>

```
        Customer customer = new Customer();
        customer.setId("1");
        customer.setCustName("Customer Name");
        customer.setEmail("customer@example.com");
        customer.setRegistrationDate(Instant.parse("2023-07-03T10:15:30.00Z"));
```

`customer` 객체를 DynamoDB 서비스에 보내기 전에 객체 `toString()` 메서드의 출력을 기록하여 향상된 클라이언트가 보내는 것과 비교하세요.

```
Customer [id=1, name=Customer Name, email=customer@example.com, regDate=2023-07-03T10:15:30Z]
```

와이어 레벨 로깅은 생성된 요청의 페이로드를 보여줍니다. 향상된 클라이언트는 데이터 클래스에서 저수준 표현을 생성했습니다. Java의 `Instant` 유형인 `regDate` 속성은 DynamoDB 문자열로 표시됩니다.

```
{
  "TableName": "Customer",
  "Item": {
    "registrationDate": {
      "S": "2023-07-03T10:15:30Z"
    },
    "id": {
      "S": "1"
    },
    "custName": {
      "S": "Customer Name"
    },
    "email": {
      "S": "customer@example.com"
    }
  }
}
```

# 기존 테이블로 작업하기
<a name="ddb-en-client-gs-existingtable"></a>

이전 단원에서는 Java 데이터 클래스로 시작하는 DynamoDB 테이블을 생성하는 방법을 살펴보았습니다. 기존 테이블이 이미 있고 향상된 클라이언트의 기능을 사용하려는 경우 해당 테이블과 함께 사용할 Java 데이터 클래스를 생성할 수 있습니다. DynamoDB 테이블을 검사하고 데이터 클래스에 필요한 주석을 추가해야 합니다.

기존 테이블로 작업하기 전에 `DynamoDbEnhanced.table()` 메서드를 호출하세요. 이 작업은 이전 예제에서 다음 명령문을 사용하여 수행되었습니다.

```
DynamoDbTable<Customer> customerTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
```

`DynamoDbTable` 인스턴스가 반환되면 기본 테이블을 사용하여 바로 작업을 시작할 수 있습니다. `DynamoDbTable.createTable()` 메서드를 호출하여 테이블을 다시 만들 필요는 없습니다.

다음 예제는 DynamoDB 테이블에서 `Customer` 인스턴스를 즉시 가져와서 이를 보여줍니다.

```
DynamoDbTable<Customer> customerTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
// The Customer table exists already and has an item with a primary key value of "1" and a sort key value of "customer@example.com".
customerTable.getItem(
        Key.builder().
                partitionValue("1").
                sortValue("customer@example.com").build());
```

**중요**  
`table()` 메서드에 사용된 테이블 이름은 기존 DynamoDB 테이블 이름과 일치해야 합니다.

# DynamoDB 향상된 클라이언트 API의 기본 사항 알아보기
<a name="ddb-en-client-use"></a>

이 항목에서는 DynamoDB 향상된 클라이언트 API의 기본 기능을 설명하고 이를 [표준 DynamoDB 클라이언트 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html)와 비교합니다.

DynamoDB 향상된 클라이언트 API를 처음 사용하는 경우 [입문 자습서](ddb-en-client-getting-started.md)를 통해 기본 클래스를 익히는 것이 좋습니다.

## Java에서 DynamoDB 항목
<a name="ddb-en-client-use-usecase"></a>

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

사용 사례에서 일관된 속성 집합을 가진 항목을 요구하는 경우 [주석이 달린 클래스](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)를 사용하거나 [빌더](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder)를 사용하여 적절한 정적 형식의 `TableSchema`을 생성하세요.

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

## 데이터 모델 클래스의 속성 유형
<a name="ddb-en-client-use-types"></a>

DynamoDB는 Java의 다양한 형식 시스템에 비해 [적은 수의 속성 유형](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)을 지원하지만 DynamoDB 향상된 클라이언트 API는 Java 클래스의 멤버를 DynamoDB 속성 유형으로 또는 DynamoDB 속성 유형에서 변환하는 메커니즘을 제공합니다.

Java 데이터 클래스의 속성 유형은 프리미티브가 아닌 객체 유형이어야 합니다. 예를 들어 항상 `long` 및 `int` 프리미티브가 아닌 `Long` 및 `Integer` 객체 데이터 형식을 사용합니다.

기본적으로 DynamoDB 향상된 클라이언트 API는 [정수](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html), [문자열](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html), [BigDecimal](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/BigDecimalAttributeConverter.html), and [인스턴트](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/InstantAsStringAttributeConverter.html)같은 다양한 유형에 대한 속성 변환기를 지원합니다. 목록은 [AttributeConverter 인터페이스의 알려진 구현 클래스](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html)에 표시됩니다. 목록에는 지도, 목록, 세트 등 다양한 유형과 컬렉션이 포함됩니다.

기본적으로 지원되지 않거나 JavaBean 규칙을 준수하지 않는 속성 유형에 대한 데이터를 저장하려면 사용자 정의 `AttributeConverter` 구현을 작성하여 변환을 수행할 수 있습니다. [예제](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-example)는 속성 변환 단원을 참조하세요.

클래스가 Java Beans 사양을 준수하는 속성 유형(또는 [변경할 수 없는 데이터 클래스](ddb-en-client-use-immut.md))의 데이터를 저장하려면 두 가지 방법을 사용할 수 있습니다.
+ 소스 파일에 액세스할 수 있는 경우 `@DynamoDbBean`(또는 `@DynamoDbImmutable`)로 클래스에 주석을 달 수 있습니다. 중첩 속성에 대해 설명하는 단원에서는 주석이 달린 클래스를 사용하는 [예제](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-anno)를 보여줍니다.
+ 속성에 대한 JavaBean 데이터 클래스의 소스 파일에 대한 액세스 권한이 없는 경우(또는 액세스 권한이 있는 클래스의 소스 파일에 주석을 달고 싶지 않은 경우) 빌더 접근 방식을 사용할 수 있습니다. 이렇게 하면 키를 정의하지 않고 테이블 스키마가 생성됩니다. 그런 다음 이 테이블 스키마를 다른 테이블 스키마에 중첩하여 매핑을 수행할 수 있습니다. 중첩 속성 단원에는 중첩 스키마 사용을 보여주는 [예제](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-builder)가 있습니다.

### Null 값
<a name="ddb-en-client-use-types-nulls"></a>

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

`updateItem` 요청에 대한 SDK의 기본 동작은 `updateItem` 메서드에서 제출하는 객체에서 null로 설정된 DynamoDB의 항목에서 속성을 제거합니다. 일부 속성 값을 업데이트하고 다른 속성 값을 변경하지 않으려면 2가지 옵션을 참고할 수 있습니다.
+ 값을 변경하기 전에 항목을 검색합니다(`getItem` 사용). SDK는 이 접근 방식을 사용하여 모든 업데이트된 값과 이전 값을 DynamoDB에 제출합니다.
+ 항목을 업데이트하기 위한 요청을 구축할 때 `[IgnoreNullsMode](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html).SCALAR_ONLY` 또는 `IgnoreNullsMode.MAPS_ONLY`를 사용합니다. 두 모드 모두 DynamoDB의 스칼라 속성을 나타내는 객체의 null 값 속성을 무시합니다. 이 가이드의 [복잡한 유형이 포함된 항목 업데이트](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-updates) 주제에는 `IgnoreNullsMode` 값에 대한 자세한 내용과 복잡한 유형으로 작업하는 방법이 포함되어 있습니다.

다음 예제는 `updateItem()` 메서드의 `ignoreNullsMode()`를 보여줍니다.

```
    public static void updateItemNullsExample() {
        Customer customer = new Customer();
        customer.setCustName("CustomerName");
        customer.setEmail("email");
        customer.setId("1");
        customer.setRegistrationDate(Instant.now());

        logger.info("Original customer: {}", customer);

        // Put item with values for all attributes.
        try {
            customerAsyncDynamoDbTable.putItem(customer).join();
        } catch (RuntimeException rte) {
            logger.error("A exception occurred during putItem: {}", rte.getCause().getMessage(), rte);
            return;
        }

        // Create a Customer instance with the same 'id' and 'email' values, but a different 'name' value.
        // Do not set the 'registrationDate' attribute.
        Customer customerForUpdate = new Customer();
        customerForUpdate.setCustName("NewName");
        customerForUpdate.setEmail("email");
        customerForUpdate.setId("1");

        // Update item without setting the 'registrationDate' property and set IgnoreNullsMode to SCALAR_ONLY.
        try {
            Customer updatedWithNullsIgnored = customerAsyncDynamoDbTable.updateItem(b -> b
                            .item(customerForUpdate)
                            .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY))
                    .join();
            logger.info("Customer updated with nulls ignored: {}", updatedWithNullsIgnored.toString());
        } catch (RuntimeException rte) {
            logger.error("An exception occurred during updateItem: {}", rte.getCause().getMessage(), rte);
            return;
        }

        // Update item without setting the registrationDate attribute and not setting ignoreNulls to true.
        try {
            Customer updatedWithNullsUsed = customerAsyncDynamoDbTable.updateItem(customerForUpdate)
                    .join();
            logger.info("Customer updated with nulls used: {}", updatedWithNullsUsed.toString());
        } catch (RuntimeException rte) {
            logger.error("An exception occurred during updateItem: {}", rte.getCause().getMessage(), rte);
        }
    }


// Logged lines. 
Original customer: Customer [id=1, name=CustomerName, email=email, regDate=2024-10-11T14:12:30.222858Z]
Customer updated with nulls ignored: Customer [id=1, name=NewName, email=email, regDate=2024-10-11T14:12:30.222858Z]
Customer updated with nulls used: Customer [id=1, name=NewName, email=email, regDate=null]
```

## DynamoDB 향상된 클라이언트의 기본 메서드
<a name="ddb-en-client-use-basic-ops"></a>

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

이 예제에서는 이전에 표시된 [`Customer` 클래스](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)을 사용합니다.

```
// 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 클라이언트 비교
<a name="ddb-en-client-use-compare"></a>

[표준](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html) 및 [고급](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html) DynamoDB 클라이언트 API를 모두 사용하면 DynamoDB 테이블을 사용하여 CRUD(생성, 읽기, 업데이트 및 삭제) 데이터 수준 작업을 수행할 수 있습니다. 클라이언트 API 간의 차이는 이를 수행하는 방법에 있습니다. 표준 클라이언트를 사용하면 저수준 데이터 속성으로 직접 작업할 수 있습니다. 향상된 클라이언트 API는 친숙한 Java 클래스를 사용하고 이면의 하위 수준 API에 매핑됩니다.

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

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

### 비교: 표준 DynamoDB 클라이언트를 사용하여 테이블 생성
<a name="ddb-en-client-use-compare-cs1"></a>

```
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 향상된 클라이언트를 사용하여 테이블 생성
<a name="ddb-en-client-use-compare-cs2"></a>

```
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 향상된 클라이언트에서 변경할 수 없는 클래스를 사용하는 예입니다. 매핑된 데이터 클래스에 변경할 수 없는 클래스를 사용하는 방법은 이 항목의 [뒷부분에서 설명합니다](ddb-en-client-use-immut.md).

### `ProductCatalog` 클래스
<a name="ddb-en-client-use-compare-cs3"></a>

```
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);
        }
    }
}
```

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

### 비교: 표준 DynamoDB 클라이언트를 사용하는 일괄 쓰기
<a name="ddb-en-client-use-compare-cs4"></a>

```
    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());
    }
```

### 비교: DynamoDB 향상된 클라이언트를 사용하는 일괄 쓰기
<a name="ddb-en-client-use-compare-cs5"></a>

```
    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());
    }
```

# 변경할 수 없는 데이터 클래스로 작업
<a name="ddb-en-client-use-immut"></a>

DynamoDB 향상된 클라이언트 API의 매핑 기능은 변경할 수 없는 데이터 클래스와 함께 작동합니다. 불변 클래스에는 접근자만 포함되며 SDK가 클래스의 인스턴스를 생성하는 데 사용하는 빌더 클래스가 필요합니다. 변경 불가능한 클래스는 [Customer 클래스에](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) 표시된 `@DynamoDbBean` 주석을 사용하는 대신 사용할 빌더 클래스를 나타내는 매개변수를 사용하는 `@DynamoDbImmutable` 주석을 사용합니다.

다음 클래스는 `Customer`의 변경할 수 없는 버전입니다.

```
package org.example.tests.model.immutable;

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.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.time.Instant;

@DynamoDbImmutable(builder = CustomerImmutable.Builder.class)
public class CustomerImmutable {
    private final String id;
    private final String name;
    private final String email;
    private final Instant regDate;

    private CustomerImmutable(Builder b) {
        this.id = b.id;
        this.email = b.email;
        this.name = b.name;
        this.regDate = b.regDate;
    }

    // This method will be automatically discovered and used by the TableSchema.
    public static Builder builder() { return new Builder(); }

    @DynamoDbPartitionKey
    public String id() { return this.id; }

    @DynamoDbSortKey
    public String email() { return this.email; }

    @DynamoDbSecondaryPartitionKey(indexNames = "customers_by_name")
    public String name() { return this.name; }

    @DynamoDbSecondarySortKey(indexNames = {"customers_by_date", "customers_by_name"})
    public Instant regDate() { return this.regDate; }

    public static final class Builder {
        private String id;
        private String email;
        private String name;
        private Instant regDate;

        // The private Builder constructor is visible to the enclosing CustomerImmutable class.
        private Builder() {}

        public Builder id(String id) { this.id = id; return this; }
        public Builder email(String email) { this.email = email; return this; }
        public Builder name(String name) { this.name = name; return this; }
        public Builder regDate(Instant regDate) { this.regDate = regDate; return this; }

        // This method will be automatically discovered and used by the TableSchema.
        public CustomerImmutable build() { return new CustomerImmutable(this); }
    }
}
```

`@DynamoDbImmutable`를 사용해 데이터 클래스에 주석을 달 때는 다음 요구 사항을 충족해야 합니다.

1. `Object.class`의 오버라이드도 아니고 `@DynamoDbIgnore`를 사용해 주석이 추가되지도 않은 모든 메서드는 DynamoDB 테이블의 속성에 대한 접근자여야 합니다.

1. 모든 접근자는 빌더 클래스에 해당하는 대소문자를 구분하는 설정자를 가져야 합니다.

1. 다음 시공 조건 중 하나만 충족해야 합니다.
   + 빌더 클래스에는 공개 기본 생성자가 있어야 합니다.
   + 데이터 클래스에는 매개 변수를 사용하지 않고 빌더 클래스의 인스턴스를 반환하는 이름이 `builder()`로 지정된 공용 정적 메서드가 있어야 합니다. 이 옵션은 변경할 수 없는 `Customer` 클래스에 표시됩니다.

1.  빌더 클래스에는 매개 변수를 사용하지 않고 변경 불가능한 클래스의 인스턴스를 반환하는 `build()`로 이름이 지정된 공용 메서드가 있어야 합니다.

변경할 수 없는 클래스를 위한 `TableSchema`를 만들려면 다음 코드 조각과 같이 `TableSchema`의 `fromImmutableClass()` 메서드를 사용하세요.

```
static final TableSchema<CustomerImmutable> customerImmutableTableSchema = 
                         TableSchema.fromImmutableClass(CustomerImmutable.class);
```

변경 가능한 클래스에서 DynamoDB 테이블을 생성할 수 있는 것처럼, 다음 코드 조각 예제와 같이 `DynamoDbTable`의 `createTable()`를 *한 번* 호출하여 변경할 수 없는 클래스에서 테이블을 생성할 수 있습니다.

```
static void createTableFromImmutable(DynamoDbEnhancedClient enhancedClient, String tableName, DynamoDbWaiter waiter){
    // First, create an in-memory representation of the table using the 'table()' method of the DynamoDb Enhanced Client.
    // 'table()' accepts a name for the table and a TableSchema instance that you created previously.
    DynamoDbTable<CustomerImmutable> customerDynamoDbTable = enhancedClient
            .table(tableName, TableSchema.fromImmutableClass(CustomerImmutable.class));
        
    // Second, call the 'createTable()' method on the DynamoDbTable instance.
    customerDynamoDbTable.createTable();
    waiter.waitUntilTableExists(b -> b.tableName(tableName));
}
```

## Lombok과 같은 타사 라이브러리를 사용
<a name="ddb-en-client-use-immut-lombok"></a>

[Project Lombok과](https://projectlombok.org/) 같은 타사 라이브러리는 변경할 수 없는 객체와 관련된 보일러러플레이트 코드를 생성하는 데 도움이 됩니다. DynamoDB 향상된 클라이언트 API는 데이터 클래스가 이 단원에 자세히 설명된 규칙을 따르는 한 이러한 라이브러리와 함께 작동합니다.

다음 예제에서는 Lombok 주석이 있는 변경 불가능한 `CustomerImmutable` 클래스를 보여줍니다. Lombok의 `onMethod` 기능이 속성 기반 DynamoDB 주석(예:`@DynamoDbPartitionKey`)을 생성된 코드에 복사하는 방법에 주목하세요.

```
@Value
@Builder
@DynamoDbImmutable(builder = Customer.CustomerBuilder.class)
public class Customer {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;

    @Getter(onMethod_=@DynamoDbSortKey)
    private String email;

    @Getter(onMethod_=@DynamoDbSecondaryPartitionKey(indexNames = "customers_by_name"))
    private String name;

    @Getter(onMethod_=@DynamoDbSecondarySortKey(indexNames = {"customers_by_date", "customers_by_name"}))
    private Instant createdDate;
}
```

# 표현식 및 조건 사용
<a name="ddb-en-client-expressions"></a>

DynamoDB 향상된 클라이언트 API의 표현식은 [DynamoDB 식](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)을 Java로 표현한 것입니다.

DynamoDB 향상된 클라이언트 API는 세 가지 유형의 식을 사용합니다.

[표현식](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)  
`Expression` 클래스는 조건과 필터를 정의할 때 사용됩니다.

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)  
이 유형의 표현식은 쿼리 작업의 [주요 조건](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions)을 나타냅니다.

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)  
이 클래스는 DynamoDB [업데이트 표현식](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)을 작성하는 데 도움이 되며, 현재 항목을 업데이트할 때 확장 프레임워크에서 사용됩니다.

## 표현 해부학
<a name="ddb-en-client-expressions-compoonents"></a>

표현식은 다음과 같이 구성됩니다.
+ 문자열 표현식(필수). 문자열에는 속성 이름 및 속성 값에 대한 자리 표시자 이름이 있는 DynamoDB 논리 표현식이 포함되어 있습니다.
+ 표현식 값 맵(일반적으로 필수).
+ 표현식 이름 맵(선택 사항).

빌더를 사용하여 다음과 같은 일반적인 형식을 취하는 `Expression` 객체를 생성합니다.

```
Expression expression = Expression.builder()
                            .expression(<String>)
                            .expressionNames(<Map>)
                            .expressionValues(<Map>)
                           .build()
```

`Expression`는 일반적으로 표현식 값의 맵이 필요합니다. 맵은 문자열 표현식의 자리 표시자 값을 제공합니다. 맵 키는 콜론(`:`) 이 붙은 자리 표시자 이름으로 구성되며 맵 값은 [AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html)의 인스턴스입니다. [AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/AttributeValues.html) 클래스에는 리터럴에서 `AttributeValue` 인스턴스를 생성할 수 있는 편리한 메서드가 있습니다. 또는 `AttributeValue.Builder`를 사용하여 `AttributeValue` 인스턴스를 생성할 수도 있습니다.

다음 코드 조각은 주석 줄 2 뒤에 두 개의 항목이 있는 맵을 보여줍니다. `expression()` 메서드에 전달된 문자열(주석 줄 1 뒤에 표시됨)에는 DynamoDB가 작업을 수행하기 전에 확인하는 자리 표시자가 포함되어 있습니다. *가격*은 허용 가능한 속성 이름이므로 이 코드 조각에는 표현식 이름 맵이 포함되어 있지 않습니다.

```
    public static void scanAsync(DynamoDbAsyncTable productCatalog) {
        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                .attributesToProject("id", "title", "authors", "price")
                .filterExpression(Expression.builder()
                        // 1. :min_value and :max_value are placeholders for the values provided by the map
                        .expression("price >= :min_value AND price <= :max_value")
                        // 2. Two values are needed for the expression and each is supplied as a map entry.
                        .expressionValues(
                                Map.of( ":min_value", numberValue(8.00),
                                        ":max_value", numberValue(400_000.00)))
                        .build())
                .build();
```

DynamoDB 테이블의 속성 이름이 예약어이거나, 숫자로 시작하거나, 공백이 포함된 경우에는 `Expression`에 대해 표현식 이름 맵이 필요합니다.

예를 들어 이전 코드 예제의 속성 이름이 `price`가 `1price`인 경우 다음의 예제와 같이 예제를 수정해야 합니다.

```
        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .filterExpression(Expression.builder()
                        .expression("#price >= :min_value AND #price <= :max_value")
                        .expressionNames( Map.of("#price", "1price") )
                        .expressionValues(
                                Map.of(":min_value", numberValue(8.00),
                                        ":max_value", numberValue(400_000.00)))
                        .build())
                .build();
```

표현식 이름의 자리 표시자는 파운드 기호(`#`)로 시작합니다. 표현식 이름 맵의 항목은 자리 표시자를 키로 사용하고 속성 이름을 값으로 사용합니다. 맵은 `expressionNames()` 메서드를 사용하여 표현식 빌더에 추가됩니다. DynamoDB는 작업을 수행하기 전에 속성 이름을 확인합니다.

문자열 표현식에 함수를 사용하는 경우에는 표현식 값이 필요하지 않습니다. 표현식 함수의 예는 `attribute_exists(<attribute_name>)`과 같습니다.

다음 예제는 [DynamoDB 함수](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)를 사용하여 `Expression`를 빌드합니다. 이 예제의 표현식 문자열은 자리 표시자를 사용하지 않습니다. 이 표현식은 `movie` 속성 값이 데이터 개체의 `movie` 속성과 같은 항목이 데이터베이스에 이미 존재하는지 확인하는 `putItem` 작업에 사용할 수 있습니다.

```
Expression exp = Expression.builder().expression("attribute_not_exists (movie)").build();
```

DynamoDB 개발자 안내서에는 DynamoDB와 함께 사용되는 [하위 수준 표현식](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)에 대한 전체 정보가 포함되어 있습니다.

## 조건 표현식 및 조건문
<a name="ddb-en-client-expressions-cond"></a>

`putItem()`, `updateItem()` 및 `deleteItem()` 메서드를 사용할 때와 트랜잭션 및 배치 작업을 사용할 때는 `[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)` 객체를 사용하여 DynamoDB가 작업을 진행하기 위해 충족해야 하는 조건을 지정합니다. 이러한 식을 조건식이라고 합니다. 예제는 이 가이드에 나와 있는 [트랜잭션 예제](ddb-en-client-use-multiop-trans.md#ddb-en-client-use-multiop-trans-writeitems-opcondition)의 `addDeleteItem()` 메서드(주석 줄 1 뒤)에 사용된 조건식을 참조하세요.

`query()` 메서드를 사용하여 작업할 경우 조건은 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)로 표현됩니다. `QueryConditional` 클래스에는 DynamoDB에서 읽을 항목을 결정하는 기준을 작성하는 데 도움이 되는 몇 가지 정적 편의 메서드가 있습니다.

`QueryConditionals`의 예제는 이 가이드 [`Query` 메서드 예제](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-query-example) 단원의 첫 번째 코드 예제를 참조하세요.

## 필터 표현식
<a name="ddb-en-client-expressions-filter"></a>

필터 표현식은 스캔 및 쿼리 작업에서 반환되는 항목을 필터링하는 데 사용됩니다.

데이터베이스에서 모든 데이터를 읽은 후 필터 표현식이 적용되므로 읽기 비용은 필터가 없는 것과 동일합니다. *Amazon DynamoDB 개발자 안내서*에는 [쿼리](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression) 및 [스캔](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression) 작업 모두에 필터 표현식을 사용하는 방법에 대한 자세한 정보가 있습니다.

다음 예제는 스캔 요청에 추가된 필터 표현식을 보여줍니다. 기준에 따라 반품되는 품목은 가격이 8.00에서 80.00 사이인 품목으로 제한됩니다.

```
        Map<String, AttributeValue> expressionValues = Map.of(
                ":min_value", numberValue(8.00),
                ":max_value", numberValue(80.00));

        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                // 1. the 'attributesToProject()' method allows you to specify which values you want returned.
                .attributesToProject("id", "title", "authors", "price")
                // 2. Filter expression limits the items returned that match the provided criteria.
                .filterExpression(Expression.builder()
                        .expression("price >= :min_value AND price <= :max_value")
                        .expressionValues(expressionValues)
                        .build())
                .build();
```

## 업데이트 표현식
<a name="ddb-en-client-expressions-update"></a>

DynamoDB 향상된 클라이언트의 `updateItem()` 메서드는 DynamoDB의 항목을 업데이트하는 표준 방법을 제공합니다. 하지만 더 많은 기능이 필요한 경우 [UpdateExpressions](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)는 DynamoDB [업데이트 표현식 구문](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)을 형식에 구애받지 않고 표현할 수 있습니다. 예를 들어 DynamoDB에서 항목을 먼저 읽지 않고 값을 늘리거나 개별 구성원을 목록에 추가하는 데 `UpdateExpressions`을 사용할 수 있습니다. 업데이트 표현식은 `updateItem()` 메서드의 사용자 지정 확장에서 사용할 수 있습니다.

업데이트 표현식을 사용하는 예제는 이 가이드의 [사용자 지정 확장 예제](ddb-en-client-extensions-custom.md)를 참조하세요.

업데이트 표현식에 대한 자세한 내용은 [Amazon DynamoDB 개발자 안내서](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)를 참조하세요.

# 페이지 매김된 결과 작업: 스캔 및 쿼리
<a name="ddb-en-client-use-multirecord"></a>

DynamoDB 향상된 클라이언트 API의 `scan`, `query` 및 `batch` 메서드는 하나 이상의 *페이지*가 포함된 응답을 반환합니다. 페이지에는 하나 이상의 항목이 포함되어 있습니다. 코드는 페이지별로 응답을 처리하거나 개별 항목을 처리할 수 있습니다.

동기 `DynamoDbEnhancedClient` 클라이언트가 반환한 페이지 단위 응답은 [PageIterable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PageIterable.html) 객체를 반환하고, 비동기 `DynamoDbEnhancedAsyncClient`에서 반환한 응답은 [PagePublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PagePublisher.html) 객체를 반환합니다.

이 단원에서는 페이지를 매긴 결과 처리를 살펴보고 스캔 및 쿼리 API를 사용하는 예제를 제공합니다.

## 테이블 스캔
<a name="ddb-en-client-use-multirecord-scan"></a>

SDK의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer)) 메서드는 동일한 이름의 [DynamoDB 작업](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html)에 해당합니다. DynamoDB 향상된 클라이언트 API는 동일한 옵션을 제공하지만 익숙한 객체 모델을 사용하고 페이지 매김을 자동으로 처리합니다.

먼저 동기 매핑 클래스 [DynamoDBTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)의 `scan` 메서드를 살펴보면서 `PageIterable` 인터페이스를 살펴봅니다.

### 동기식 API를 사용
<a name="ddb-en-client-use-multirecord-scan-sync"></a>

다음 예제는 [표현식](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)을 사용하여 반환되는 항목을 필터링하는 `scan` 메서드를 보여줍니다. [ProductCatalog](ddb-en-client-use.md#ddb-en-client-use-compare-cs3)는 이전에 표시된 모델 객체입니다.

주석 줄 2 뒤에 표시되는 필터링 표현식은 반환되는 `ProductCatalog` 항목을 가격 값이 8.00\$180.00인 항목으로 제한합니다.

또한이 예제에서는 주석 줄 1 다음에 표시된 `attributesToProject` 메서드를 사용하여 `isbn` 값을 제외합니다.

주석 줄 3 이후에는 `scan` 메서드가 `PageIterable` 객체, `pagedResults`를 반환합니다. `PageIterable`의 `stream` 메서드는 페이지를 처리하는 데 사용할 수 있는 [https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html) 객체를 반환합니다. 이 예제는 페이지 수를 세고 기록합니다.

이 예제는 주석 줄 4부터 시작하여 `ProductCatalog` 항목 액세스의 두 가지 변형을 보여줍니다. 주석 줄 4a 이후의 버전은 각 페이지를 스트리밍하고 각 페이지의 항목을 정렬하고 로깅합니다. 주석 줄 4b 이후의 버전은 페이지 반복을 건너뛰고 항목에 직접 액세스합니다.

`PageIterable` 인터페이스는 2개의 상위 인터페이스 [https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) 및 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html)로 결과를 처리하는 다양한 방법을 제공합니다. `Iterable`은 `forEach`, `iterator` 및 `spliterator` 메서드를 가져오고 `SdkIterable`은 `stream` 메서드를 가져옵니다.

```
    public static void scanSync(DynamoDbTable<ProductCatalog> productCatalog) {

        Map<String, AttributeValue> expressionValues = Map.of(
                ":min_value", numberValue(8.00),
                ":max_value", numberValue(80.00));

        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                // 1. the 'attributesToProject()' method allows you to specify which values you want returned.
                .attributesToProject("id", "title", "authors", "price")
                // 2. Filter expression limits the items returned that match the provided criteria.
                .filterExpression(Expression.builder()
                        .expression("price >= :min_value AND price <= :max_value")
                        .expressionValues(expressionValues)
                        .build())
                .build();

        // 3. A PageIterable object is returned by the scan method.
        PageIterable<ProductCatalog> pagedResults = productCatalog.scan(request);
        logger.info("page count: {}", pagedResults.stream().count());

        // 4. Log the returned ProductCatalog items using two variations.
        // 4a. This version sorts and logs the items of each page.
        pagedResults.stream().forEach(p -> p.items().stream()
                .sorted(Comparator.comparing(ProductCatalog::price))
                .forEach(
                        item -> logger.info(item.toString())
                ));
        // 4b. This version sorts and logs all items for all pages.
        pagedResults.items().stream()
                .sorted(Comparator.comparing(ProductCatalog::price))
                .forEach(
                        item -> logger.info(item.toString())
                );
    }
```

### 비동기 API 사용
<a name="ddb-en-client-use-multirecord-scan-async"></a>

비동기 `scan` 메서드는 결과를 `PagePublisher` 객체로 반환합니다. `PagePublisher` 인터페이스에는 응답 페이지를 처리하는 데 사용할 수 있는 두 가지 `subscribe` 메서드가 있습니다. 한 가지 `subscribe` 메서드는 `org.reactivestreams.Publisher` 상위 인터페이스에서 가져온 것입니다. 이 첫 번째 옵션을 사용하여 페이지를 처리하려면 `subscribe` 메서드에 `[Subscriber](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Subscriber.html)` 인스턴스를 전달하세요. 다음 예제는 `subscribe` 메서드 사용을 보여 줍니다.

두 번째 `subscribe` 메서드는 [SdkPublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html) 인터페이스에서 가져온 것입니다. `subscribe`의 이 버전에서는 `Subscriber`가 아닌 [https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html)를 받아들입니다. 이 `subscribe` 메서드 변형은 다음 두 번째 예제에 나와 있습니다.

다음 예제는 이전 예제와 동일한 필터 표현식을 사용하는 `scan` 메서드의 비동기 버전을 보여줍니다.

주석 줄 3번 다음에 `DynamoDbAsyncTable.scan`는 `PagePublisher` 객체를 반환합니다. 다음 줄에서 코드는 `org.reactivestreams.Subscriber` 인터페이스의 인스턴스를 만들고, `ProductCatalogSubscriber`는 주석 줄 4 뒤 `PagePublisher`를 구독합니다.

`Subscriber` 객체는 `ProductCatalogSubscriber` 클래스 예제의 주석 줄 8 다음에 있는 `onNext` 메서드의 각 페이지에서 `ProductCatalog` 항목을 수집합니다. 항목은 전용 `List` 변수에 저장되며 `ProductCatalogSubscriber.getSubscribedItems()` 메서드를 사용하여 호출 코드에서 액세스할 수 있습니다. 이는 주석 줄 5 이후에 호출됩니다.

목록이 검색되면 코드는 모든 `ProductCatalog` 항목을 가격별로 정렬하고 각 항목을 기록합니다.

`ProductCatalogSubscriber` 클래스의 [CountDownLatch](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html)는 모든 항목이 목록에 추가될 때까지 호출 스레드를 차단한 다음 주석 줄 5 이후에 계속합니다.

```
    public static void scanAsync(DynamoDbAsyncTable productCatalog) {
        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                .attributesToProject("id", "title", "authors", "price")
                .filterExpression(Expression.builder()
                        // 1. :min_value and :max_value are placeholders for the values provided by the map
                        .expression("price >= :min_value AND price <= :max_value")
                        // 2. Two values are needed for the expression and each is supplied as a map entry.
                        .expressionValues(
                                Map.of( ":min_value", numberValue(8.00),
                                        ":max_value", numberValue(400_000.00)))
                        .build())
                .build();

        // 3. A PagePublisher object is returned by the scan method.
        PagePublisher<ProductCatalog> pagePublisher = productCatalog.scan(request);
        ProductCatalogSubscriber subscriber = new ProductCatalogSubscriber();
        // 4. Subscribe the ProductCatalogSubscriber to the PagePublisher.
        pagePublisher.subscribe(subscriber);
        // 5. Retrieve all collected ProductCatalog items accumulated by the subscriber.
        subscriber.getSubscribedItems().stream()
                .sorted(Comparator.comparing(ProductCatalog::price))
                .forEach(item ->
                        logger.info(item.toString()));
        // 6. Use a Consumer to work through each page.
        pagePublisher.subscribe(page -> page
                        .items().stream()
                        .sorted(Comparator.comparing(ProductCatalog::price))
                        .forEach(item ->
                                logger.info(item.toString())))
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
        // 7. Use a Consumer to work through each ProductCatalog item.
        pagePublisher.items()
                .subscribe(product -> logger.info(product.toString()))
                .exceptionally(failure -> {
                    logger.error("ERROR  - ", failure);
                    return null;
                })
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
    }
```

```
    private static class ProductCatalogSubscriber implements Subscriber<Page<ProductCatalog>> {
        private CountDownLatch latch = new CountDownLatch(1);
        private Subscription subscription;
        private List<ProductCatalog> itemsFromAllPages = new ArrayList<>();

        @Override
        public void onSubscribe(Subscription sub) {
            subscription = sub;
            subscription.request(1L);
            try {
                latch.await(); // Called by main thread blocking it until latch is released.
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void onNext(Page<ProductCatalog> productCatalogPage) {
            // 8. Collect all the ProductCatalog instances in the page, then ask the publisher for one more page.
            itemsFromAllPages.addAll(productCatalogPage.items());
            subscription.request(1L);
        }

        @Override
        public void onError(Throwable throwable) {
        }

        @Override
        public void onComplete() {
            latch.countDown(); // Call by subscription thread; latch releases.
        }

        List<ProductCatalog> getSubscribedItems() {
            return this.itemsFromAllPages;
        }
    }
```

다음 코드 조각 예제는 주석 줄 6을 다음에 `Consumer`를 받아들이는 `PagePublisher.subscribe` 메서드의 버전을 사용합니다. Java 람다 매개변수는 각 항목을 추가로 처리하는 페이지를 사용합니다. 이 예에서는 각 페이지가 처리되고 각 페이지의 항목이 정렬된 후 기록됩니다.

```
        // 6. Use a Consumer to work through each page.
        pagePublisher.subscribe(page -> page
                        .items().stream()
                        .sorted(Comparator.comparing(ProductCatalog::price))
                        .forEach(item ->
                                logger.info(item.toString())))
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
```

`PagePublisher`의 `items` 메서드는 모델 인스턴스를 언래핑하여 코드가 항목을 직접 처리할 수 있도록 합니다. 이 접근 방법은 다음 코드 조각에 나와 있습니다.

```
        // 7. Use a Consumer to work through each ProductCatalog item.
        pagePublisher.items()
                .subscribe(product -> logger.info(product.toString()))
                .exceptionally(failure -> {
                    logger.error("ERROR  - ", failure);
                    return null;
                })
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
```

## 테이블 쿼리
<a name="ddb-en-client-use-multirecord-query"></a>

DynamoDB 향상된 클라이언트를 사용하여 테이블을 쿼리하고 특정 기준과 일치하는 여러 항목을 검색할 수 있습니다. [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)) 메서드는 데이터 클래스에 정의된 `@DynamoDbPartitionKey` 및 `@DynamoDbSortKey` 주석(선택 사항)을 사용하여 프라이머리 키 값을 기반으로 항목을 찾습니다.

`query()` 메서드에는 파티션 키 값이 필요하며 경우에 따라 정렬 키 조건을 수락하여 결과를 추가로 구체화합니다. `scan` API와 마찬가지로 쿼리는 동기식 호출의 `PageIterable`을 반환하고 비동기식 직접 호출의 `PagePublisher`를 반환합니다.

### `Query` 메서드 예제
<a name="ddb-en-client-use-multirecord-query-example"></a>

다음 `query()` 메서드 코드 예제에서는 `MovieActor` 클래스를 사용합니다. 데이터 클래스는 파티션 키의 **`movie`** 속성과 정렬 키의 **`actor`** 속성으로 구성된 복합 프라이머리 키를 정의합니다.

#### `MovieActor` 클래스
<a name="ddb-en-client-use-movieactor-class"></a>

```
package org.example.tests.model;

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
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.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.util.Objects;

@DynamoDbBean
public class MovieActor implements Comparable<MovieActor> {

    private String movieName;
    private String actorName;
    private String actingAward;
    private Integer actingYear;
    private String actingSchoolName;

    @DynamoDbPartitionKey
    @DynamoDbAttribute("movie")
    public String getMovieName() {
        return movieName;
    }

    public void setMovieName(String movieName) {
        this.movieName = movieName;
    }

    @DynamoDbSortKey
    @DynamoDbAttribute("actor")
    public String getActorName() {
        return actorName;
    }

    public void setActorName(String actorName) {
        this.actorName = actorName;
    }

    @DynamoDbSecondaryPartitionKey(indexNames = "acting_award_year")
    @DynamoDbAttribute("actingaward")
    public String getActingAward() {
        return actingAward;
    }

    public void setActingAward(String actingAward) {
        this.actingAward = actingAward;
    }

    @DynamoDbSecondarySortKey(indexNames = {"acting_award_year", "movie_year"})
    @DynamoDbAttribute("actingyear")
    public Integer getActingYear() {
        return actingYear;
    }

    public void setActingYear(Integer actingYear) {
        this.actingYear = actingYear;
    }

    @DynamoDbAttribute("actingschoolname")
    public String getActingSchoolName() {
        return actingSchoolName;
    }

    public void setActingSchoolName(String actingSchoolName) {
        this.actingSchoolName = actingSchoolName;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("MovieActor{");
        sb.append("movieName='").append(movieName).append('\'');
        sb.append(", actorName='").append(actorName).append('\'');
        sb.append(", actingAward='").append(actingAward).append('\'');
        sb.append(", actingYear=").append(actingYear);
        sb.append(", actingSchoolName='").append(actingSchoolName).append('\'');
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MovieActor that = (MovieActor) o;
        return Objects.equals(movieName, that.movieName) && Objects.equals(actorName, that.actorName) && Objects.equals(actingAward, that.actingAward) && Objects.equals(actingYear, that.actingYear) && Objects.equals(actingSchoolName, that.actingSchoolName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(movieName, actorName, actingAward, actingYear, actingSchoolName);
    }

    @Override
    public int compareTo(MovieActor o) {
        if (this.movieName.compareTo(o.movieName) != 0){
            return this.movieName.compareTo(o.movieName);
        } else {
            return this.actorName.compareTo(o.actorName);
        }
    }
}
```

다음 항목에 대한 쿼리를 따르는 코드 예제.

#### `MovieActor` 테이블 내 항목
<a name="ddb-en-client-use-movieactor-items"></a>

```
MovieActor{movieName='movie01', actorName='actor0', actingAward='actingaward0', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
MovieActor{movieName='movie02', actorName='actor0', actingAward='actingaward0', actingYear=2002, actingSchoolName='null'}
MovieActor{movieName='movie02', actorName='actor1', actingAward='actingaward1', actingYear=2002, actingSchoolName='actingschool1'}
MovieActor{movieName='movie02', actorName='actor2', actingAward='actingaward2', actingYear=2002, actingSchoolName='actingschool2'}
MovieActor{movieName='movie02', actorName='actor3', actingAward='actingaward3', actingYear=2002, actingSchoolName='null'}
MovieActor{movieName='movie02', actorName='actor4', actingAward='actingaward4', actingYear=2002, actingSchoolName='actingschool4'}
MovieActor{movieName='movie03', actorName='actor0', actingAward='actingaward0', actingYear=2003, actingSchoolName='null'}
MovieActor{movieName='movie03', actorName='actor1', actingAward='actingaward1', actingYear=2003, actingSchoolName='actingschool1'}
MovieActor{movieName='movie03', actorName='actor2', actingAward='actingaward2', actingYear=2003, actingSchoolName='actingschool2'}
MovieActor{movieName='movie03', actorName='actor3', actingAward='actingaward3', actingYear=2003, actingSchoolName='null'}
MovieActor{movieName='movie03', actorName='actor4', actingAward='actingaward4', actingYear=2003, actingSchoolName='actingschool4'}
```

다음 코드는 `keyEqual`(주석 줄 1 이후) 및 `sortGreaterThanOrEqualTo`(주석 줄 1a 이후)의 2가지 `QueryConditional` 인스턴스를 정의합니다.

#### 파티션 키로 항목 쿼리
<a name="keyEqual-query-conditional-example"></a>

`keyEqual` 인스턴스는 파티션 키 값이 **`movie01`**인 항목과 일치합니다.

또한 이 예제는 주석 줄 2 이후 **`actingschoolname`**이 없는 항목을 필터링하는 필터 표현식을 정의합니다.

`QueryEnhancedRequest`는 쿼리에 대한 키 조건과 필터 표현식을 통합합니다.

```
    public static void query(DynamoDbTable movieActorTable) {

        // 1. Define a QueryConditional instance to return items matching a partition value.
        QueryConditional keyEqual = QueryConditional.keyEqualTo(b -> b.partitionValue("movie01"));
        // 1a. Define a QueryConditional that adds a sort key criteria to the partition value criteria.
        QueryConditional sortGreaterThanOrEqualTo = QueryConditional.sortGreaterThanOrEqualTo(b -> b.partitionValue("movie01").sortValue("actor2"));
        // 2. Define a filter expression that filters out items whose attribute value is null.
        final Expression filterOutNoActingschoolname = Expression.builder().expression("attribute_exists(actingschoolname)").build();

        // 3. Build the query request.
        QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder()
                .queryConditional(keyEqual)
                .filterExpression(filterOutNoActingschoolname)
                .build();
        // 4. Perform the query using the "keyEqual" conditional and filter expression.
        PageIterable<MovieActor> pagedResults = movieActorTable.query(tableQuery);
        logger.info("page count: {}", pagedResults.stream().count()); // Log  number of pages.

        pagedResults.items().stream()
                .sorted()
                .forEach(
                        item -> logger.info(item.toString()) // Log the sorted list of items.
                );
```

**Example - `keyEqual` 쿼리 조건부를 사용한 출력**  
다음은 메서드 실행 결과입니다. 출력에는 **movie01**의 `movieName` 값이 있는 항목이 표시되고 **`null`**과 같은 `actingSchoolName`이 있는 항목은 표시되지 않습니다.  

```
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:46 - page count: 1
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
```

#### 파티션 키 및 정렬 키로 항목 쿼리
<a name="sort-type-query-conditional-example"></a>

`sortGreaterThanOrEqualTo` `QueryConditional`은 **actor2**보다 크거나 같은 값에 정렬 키 조건을 추가하여 파티션 키 일치(**movie01**)를 구체화합니다.

`sort`로 시작하는[`QueryConditional` 메서드](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)는 정렬 키 값을 기반으로 비교를 통해 쿼리를 일치시키고 더 구체화해야 합니다. 메서드 이름의 `Sort`는 결과가 정렬되었음을 의미하지 않지만 비교 시 정렬 키 값이 사용됩니다.

다음 코드 조각에서는 주석 줄 3 이후, 앞서 표시된 쿼리 요청을 변경합니다. 이 코드 조각은 ‘keyEqual’ 쿼리 조건을 주석 줄 1a 이후 정의된 ‘sortGreaterThanOrEqualTo’ 쿼리 조건으로 대체합니다. 다음 코드는 또한 필터 표현식을 제거합니다.

```
        QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder()
                .queryConditional(sortGreaterThanOrEqualTo).build();
```

**Example - `sortGreaterThanOrEqualTo` 쿼리 조건부를 사용한 출력**  
다음 출력은 쿼리 결과를 표시합니다. 쿼리는 `movieName` 값이 **movie01과** 같은 항목을 반환하고 **actor2**보다 크거나 같은 `actorName` 값을 가진 항목만 반환합니다. 필터가 제거되었으므로 쿼리는 `actingSchoolName` 속성 값이 없는 항목을 반환합니다.  

```
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:46 - page count: 1
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'}
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
```

# 배치 작업 수행
<a name="ddb-en-client-use-multiop-batch"></a>

DynamoDB 향상된 클라이언트 API는 [`batchGetItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) 및 [`batchWriteItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchWriteItem(java.util.function.Consumer))라는 두 가지 배치 메서드를 제공합니다.

## `batchGetItem()` 예
<a name="ddb-en-client-use-multiop-batch-get"></a>

이 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) 메서드를 사용하면 전체 요청 한 번으로 여러 테이블에서 최대 100개의 개별 항목을 검색할 수 있습니다. 다음 예제에서는 이전에 표시된 [`Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) 및 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) 데이터 클래스를 사용합니다.

1행과 2행 뒤에 오는 예제에서는 `[ReadBatch](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ReadBatch.html)` 객체를 빌드하고 나중에 이 객체를 `batchGetItem()` 메서드에 주석 줄 3 다음에 파라미터로 추가합니다.

주석 줄 1 뒤의 코드는 `Customer` 테이블에서 읽을 배치를 빌드합니다. 주석 줄 1a 뒤의 코드는 프라이머리 키 값과 정렬 키 값을 사용하여 읽을 항목을 지정하는 `[GetItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/GetItemEnhancedRequest.Builder.html)` 빌더를 사용하는 방법을 보여줍니다. 데이터 클래스에 복합 키가 있는 경우 파티션 키 값과 정렬 키 값을 모두 제공해야 합니다.

항목을 요청하기 위해 키 값을 지정하는 것과 달리 주석 줄 1b 다음에 표시된 대로 데이터 클래스를 사용하여 항목을 요청할 수 있습니다. SDK는 요청을 제출하기 전에 백그라운드에서 키 값을 추출합니다.

2a 이후의 두 명령문에서 볼 수 있듯이 키 기반 접근 방식을 사용하여 항목을 지정하는 경우 DynamoDB가 [매우 일관된 읽기](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html)를 수행하도록 지정할 수도 있습니다. `consistentRead()` 메서드를 사용하는 경우 동일한 테이블에 대해 요청된 모든 항목에 이 메서드를 사용해야 합니다.

DynamoDB에서 찾은 항목을 검색하려면 주석 4줄 뒤에 표시된 `[resultsForTable() ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#resultsForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` 메서드를 사용하세요. 요청에서 읽은 각 테이블의 메서드를 호출합니다. `resultsForTable()`는 임의의 `java.util.List` 메서드를 사용하여 처리할 수 있는 검색된 항목 목록을 반환합니다. 이 예제는 각 항목을 기록합니다.

DynamoDB에서 처리하지 않은 항목을 검색하려면 주석 5줄 다음에 있는 접근 방식을 사용하세요. `BatchGetResultPage` 클래스에는 처리되지 않은 각 키에 액세스할 수 있는 `[unprocessedKeysForTable()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#unprocessedKeysForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` 메서드가 있습니다. [BatchGetItem API 참조](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)에는 처리되지 않은 항목이 발생하는 상황에 대한 자세한 정보가 있습니다.

```
    public static void batchGetItemExample(DynamoDbEnhancedClient enhancedClient,
                                           DynamoDbTable<Customer> customerTable,
                                           DynamoDbTable<MovieActor> movieActorTable) {

        Customer customer2 = new Customer();
        customer2.setId("2");
        customer2.setEmail("cust2@example.org");

        // 1. Build a batch to read from the Customer table.
        ReadBatch customerBatch = ReadBatch.builder(Customer.class)
                .mappedTableResource(customerTable)
                // 1a. Specify the primary key value and sort key value for the item.
                .addGetItem(b -> b.key(k -> k.partitionValue("1").sortValue("cust1@orgname.org")))
                // 1b. Alternatively, supply a data class instances to provide the primary key values.
                .addGetItem(customer2)
                .build();

        // 2. Build a batch to read from the MovieActor table.
        ReadBatch moveActorBatch = ReadBatch.builder(MovieActor.class)
                .mappedTableResource(movieActorTable)
                // 2a. Call consistentRead(Boolean.TRUE) for each item for the same table.
                .addGetItem(b -> b.key(k -> k.partitionValue("movie01").sortValue("actor1")).consistentRead(Boolean.TRUE))
                .addGetItem(b -> b.key(k -> k.partitionValue("movie01").sortValue("actor4")).consistentRead(Boolean.TRUE))
                .build();

        // 3. Add ReadBatch objects to the request.
        BatchGetResultPageIterable resultPages = enhancedClient.batchGetItem(b -> b.readBatches(customerBatch, moveActorBatch));

        // 4. Retrieve the successfully requested items from each table.
        resultPages.resultsForTable(customerTable).forEach(item -> logger.info(item.toString()));
        resultPages.resultsForTable(movieActorTable).forEach(item -> logger.info(item.toString()));

        // 5. Retrieve the keys of the items requested but not processed by the service.
        resultPages.forEach((BatchGetResultPage pageResult) -> {
            pageResult.unprocessedKeysForTable(customerTable).forEach(key -> logger.info("Unprocessed item key: " + key.toString()));
            pageResult.unprocessedKeysForTable(movieActorTable).forEach(key -> logger.info("Unprocessed item key: " + key.toString()));
        });
    }
```

예제 코드를 실행하기 전에 두 테이블에 다음 항목이 있다고 가정해 보세요.

### 테이블 내 항목
<a name="ddb-en-client-use-multiop-batch-get-tableitems"></a>

```
Customer [id=1, name=CustName1, email=cust1@example.org, regDate=2023-03-31T15:46:27.688Z]
Customer [id=2, name=CustName2, email=cust2@example.org, regDate=2023-03-31T15:46:28.688Z]
Customer [id=3, name=CustName3, email=cust3@example.org, regDate=2023-03-31T15:46:29.688Z]
Customer [id=4, name=CustName4, email=cust4@example.org, regDate=2023-03-31T15:46:30.688Z]
Customer [id=5, name=CustName5, email=cust5@example.org, regDate=2023-03-31T15:46:31.689Z]
MovieActor{movieName='movie01', actorName='actor0', actingAward='actingaward0', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
```

다음 출력은 주석 라인 4 이후에 반환되고 기록된 항목을 보여줍니다.

```
Customer [id=1, name=CustName1, email=cust1@example.org, regDate=2023-03-31T15:46:27.688Z]
Customer [id=2, name=CustName2, email=cust2@example.org, regDate=2023-03-31T15:46:28.688Z]
MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
```

## `batchWriteItem()` 예
<a name="ddb-en-client-use-multiop-batch-write"></a>

이 메서드는 하나 이상의 테이블에 여러 항목을 추가하거나 삭제합니다. 요청에 최대 25개의 개별 넣기 또는 삭제 작업을 지정할 수 있습니다. 다음 예제에서는 이전에 표시된 [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) 및 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) 데이터 클래스를 사용합니다.

`WriteBatch` 객체는 주석 라인 1과 2 다음에 빌드됩니다. `ProductCatalog` 테이블의 경우 코드는 항목 하나를 추가하고 항목 하나를 삭제합니다. 주석 줄 2 뒤에 있는 `MovieActor` 테이블의 경우 코드는 항목 두 개를 넣고 한 개를 삭제합니다.

`batchWriteItem` 메서드는 주석 줄 3 이후에 호출됩니다. `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteItemEnhancedRequest.Builder.html)` 파라미터는 각 테이블에 대한 일괄 요청을 제공합니다.

반환된 `[BatchWriteResult](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteResult.html)` 객체는 처리되지 않은 요청을 볼 수 있는 각 작업에 대해 별도의 메서드를 제공합니다. 주석 줄 4a 뒤의 코드는 처리되지 않은 삭제 요청에 대한 키를 제공하고 주석 줄 4b 뒤의 코드는 처리되지 않은 put 항목을 제공합니다.

```
    public static void batchWriteItemExample(DynamoDbEnhancedClient enhancedClient,
                                             DynamoDbTable<ProductCatalog> catalogTable,
                                             DynamoDbTable<MovieActor> movieActorTable) {

        // 1. Build a batch to write to the ProductCatalog table.
        WriteBatch products = WriteBatch.builder(ProductCatalog.class)
                .mappedTableResource(catalogTable)
                .addPutItem(b -> b.item(getProductCatItem1()))
                .addDeleteItem(b -> b.key(k -> k
                        .partitionValue(getProductCatItem2().id())
                        .sortValue(getProductCatItem2().title())))
                .build();

        // 2. Build a batch to write to the MovieActor table.
        WriteBatch movies = WriteBatch.builder(MovieActor.class)
                .mappedTableResource(movieActorTable)
                .addPutItem(getMovieActorYeoh())
                .addPutItem(getMovieActorBlanchettPartial())
                .addDeleteItem(b -> b.key(k -> k
                        .partitionValue(getMovieActorStreep().getMovieName())
                        .sortValue(getMovieActorStreep().getActorName())))
                .build();

        // 3. Add WriteBatch objects to the request.
        BatchWriteResult batchWriteResult = enhancedClient.batchWriteItem(b -> b.writeBatches(products, movies));
        // 4. Retrieve keys for items the service did not process.
        // 4a. 'unprocessedDeleteItemsForTable()' returns keys for delete requests that did not process.
        if (batchWriteResult.unprocessedDeleteItemsForTable(movieActorTable).size() > 0) {
            batchWriteResult.unprocessedDeleteItemsForTable(movieActorTable).forEach(key ->
                    logger.info(key.toString()));
        }
        // 4b. 'unprocessedPutItemsForTable()' returns keys for put requests that did not process.
        if (batchWriteResult.unprocessedPutItemsForTable(catalogTable).size() > 0) {
            batchWriteResult.unprocessedPutItemsForTable(catalogTable).forEach(key ->
                    logger.info(key.toString()));
        }
    }
```

다음 도우미 메서드는 업로드 및 삭제 작업을 위한 모델 객체를 제공합니다.

### 도우미 메서드
<a name="ddb-en-client-use-multiop-batch-write-helpers"></a>

```
 1.     public static ProductCatalog getProductCatItem1() {
 2.         return ProductCatalog.builder()
 3.                 .id(2)
 4.                 .isbn("1-565-85698")
 5.                 .authors(new HashSet<>(Arrays.asList("a", "b")))
 6.                 .price(BigDecimal.valueOf(30.22))
 7.                 .title("Title 55")
 8.                 .build();
 9.     }
10. 
11.     public static ProductCatalog getProductCatItem2() {
12.         return ProductCatalog.builder()
13.                 .id(4)
14.                 .price(BigDecimal.valueOf(40.00))
15.                 .title("Title 1")
16.                 .build();
17.     }  
18. 
19.     public static MovieActor getMovieActorBlanchettPartial() {
20.         MovieActor movieActor = new MovieActor();
21.         movieActor.setActorName("Cate Blanchett");
22.         movieActor.setMovieName("Blue Jasmine");
23.         movieActor.setActingYear(2023);
24.         movieActor.setActingAward("Best Actress");
25.         return movieActor;
26.     }
27. 
28.     public static MovieActor getMovieActorStreep() {
29.         MovieActor movieActor = new MovieActor();
30.         movieActor.setActorName("Meryl Streep");
31.         movieActor.setMovieName("Sophie's Choice");
32.         movieActor.setActingYear(1982);
33.         movieActor.setActingAward("Best Actress");
34.         movieActor.setActingSchoolName("Yale School of Drama");
35.         return movieActor;
36.     }
37. 
38.     public static MovieActor getMovieActorYeoh(){
39.         MovieActor movieActor = new MovieActor();
40.         movieActor.setActorName("Michelle Yeoh");
41.         movieActor.setMovieName("Everything Everywhere All at Once");
42.         movieActor.setActingYear(2023);
43.         movieActor.setActingAward("Best Actress");
44.         movieActor.setActingSchoolName("Royal Academy of Dance");
45.         return movieActor;
46.     }
```

예제 코드를 실행하기 전에 테이블에 다음 항목이 포함되어 있다고 가정해 보겠습니다.

```
MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
```

예제 코드가 끝나면 테이블에는 다음 항목이 포함됩니다.

```
MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='null'}
MovieActor{movieName='Everything Everywhere All at Once', actorName='Michelle Yeoh', actingAward='Best Actress', actingYear=2023, actingSchoolName='Royal Academy of Dance'}
ProductCatalog{id=2, title='Title 55', isbn='1-565-85698', authors=[a, b], price=30.22}
```

`MovieActor` 테이블에서 `Blue Jasmine` 영화 항목이 `getMovieActorBlanchettPartial()` 도우미 메서드를 통해 획득한 put 요청에 사용된 항목으로 대체되었음을 알 수 있습니다. 데이터 bean 속성 값이 제공되지 않은 경우 데이터베이스의 값이 제거됩니다. 이것이 `Blue Jasmine` 영화 항목의 결과 `actingSchoolName`가 null이 되는 이유입니다.

**참고**  
API 설명서에는 조건식을 사용할 수 있고 사용된 용량 및 컬렉션 지표를 개별 [PUT](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PutItemEnhancedRequest.html) 및 [DELETE](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/DeleteItemEnhancedRequest.html) 요청과 함께 반환할 수 있다고 나와 있지만 일괄 쓰기 시나리오에서는 그렇지 않습니다. 일괄 작업의 성능을 향상시키기 위해 이러한 개별 옵션은 무시됩니다.

# 트랜잭션 작업 수행
<a name="ddb-en-client-use-multiop-trans"></a>

DynamoDB 향상된 클라이언트 API는 `transactGetItems()` 및 `transactWriteItems()` 메서드를 제공합니다. Java용 SDK의 트랜잭션 방법은 DynamoDB 테이블에 ACID(원자성, 일관성, 격리 및 내구성)를 제공하여 애플리케이션에서 데이터 정확성을 유지하는 데 도움이 됩니다.

## `transactGetItems()` 예
<a name="ddb-en-client-use-multiop-trans-getitems"></a>

`[transactGetItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactGetItems(java.util.function.Consumer))` 메서드는 항목에 대한 개별 요청을 최대 100개까지 수락합니다. 단일 아토믹 트랜잭션으로 모든 항목을 읽습니다. *Amazon DynamoDB 개발자 안내서*에는 [`transactGetItems()` 메서드 실패를 유발하는 조건](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txgetitems)에 대한 정보와 `[transactGetItem()](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-isolation)` 호출 시 사용되는 격리 수준에 대한 정보가 있습니다.

다음 예제의 주석 줄 1 다음에, 코드는 `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html)` 매개변수를 사용하여 `transactGetItems()` 메서드를 호출합니다. SDK가 최종 요청을 생성하는 데 사용할 키 값이 포함된 데이터 객체를 사용하여 빌더 `[addGetItem()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html#addGetItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource,T))`를 세 번 호출합니다.

요청은 주석 줄 2 다음에 `[Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html)` 객체 목록을 반환합니다. 반환되는 문서 목록에는 요청된 순서와 동일한 순서로 항목 데이터의 null이 아닌 [Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html) 인스턴스가 포함되어 있습니다. `[Document.getItem(MappedTableResource<T> mappedTableResource)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html#getItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` 메서드는 항목 데이터가 반환된 경우 형식화되지 않은 `Document` 객체를 형식이 지정된 Java 객체로 변환하고, 그렇지 않으면 null을 반환합니다.

```
    public static void transactGetItemsExample(DynamoDbEnhancedClient enhancedClient,
                                               DynamoDbTable<ProductCatalog> catalogTable,
                                               DynamoDbTable<MovieActor> movieActorTable) {

        // 1. Request three items from two tables using a builder.
        final List<Document> documents = enhancedClient.transactGetItems(b -> b
                .addGetItem(catalogTable, Key.builder().partitionValue(2).sortValue("Title 55").build())
                .addGetItem(movieActorTable, Key.builder().partitionValue("Sophie's Choice").sortValue("Meryl Streep").build())
                .addGetItem(movieActorTable, Key.builder().partitionValue("Blue Jasmine").sortValue("Cate Blanchett").build())
                .build());

        // 2. A list of Document objects is returned in the same order as requested.
        ProductCatalog title55 = documents.get(0).getItem(catalogTable);
        if (title55 != null) {
            logger.info(title55.toString());
        }

        MovieActor sophiesChoice = documents.get(1).getItem(movieActorTable);
        if (sophiesChoice != null) {
            logger.info(sophiesChoice.toString());
        }

        // 3. The getItem() method returns null if the Document object contains no item from DynamoDB.
        MovieActor blueJasmine = documents.get(2).getItem(movieActorTable);
        if (blueJasmine != null) {
            logger.info(blueJasmine.toString());
        }
    }
```

코드 예제가 실행되기 전에 DynamoDB 테이블에는 다음 항목이 포함되어 있습니다.

```
ProductCatalog{id=2, title='Title 55', isbn='orig_isbn', authors=[b, g], price=10}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
```

다음 출력이 반환됩니다. 항목을 요청했지만 찾을 수 없는 경우 이름이 `Blue Jasmine`로 지정된 영화에 대한 요청의 경우와 마찬가지로 항목이 반환되지 않습니다.

```
ProductCatalog{id=2, title='Title 55', isbn='orig_isbn', authors=[b, g], price=10}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
```

## `transactWriteItems()` 예제
<a name="ddb-en-client-use-multiop-trans-writeitems"></a>

`[transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))`는 여러 테이블에 걸친 단일 아토믹 트랜잭션으로 최대 100개의 추가, 업데이트 또는 삭제 작업을 허용합니다. *Amazon DynamoDB 개발자 안내서*에는 [기본 DynamoDB 서비스 작업](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems)의 제한 및 장애 조건에 대한 세부 정보가 포함되어 있습니다.

### 기본 예제
<a name="ddb-en-client-use-multiop-trans-writeitems-basic"></a>

다음 예제에서는 2개의 테이블에 대해 4개의 작업이 요청됩니다. 해당 모델 클래스 [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) 및 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class)는 이전에 표시된 바 있습니다.

세 가지 가능한 작업(put, update, delete)은 각각 전용 요청 매개변수를 사용하여 세부 정보를 지정합니다.

주석 줄 1 뒤의 코드는 `addPutItem()` 메서드의 단순한 변형을 보여줍니다. 메서드는 입력할 `[MappedTableResource](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.html)` 개체와 데이터 객체 인스턴스를 받아들입니다. 주석 줄 2 뒤의 명령문은 `[TransactPutItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactPutItemEnhancedRequest.html)` 인스턴스를 허용하는 변형을 보여줍니다. 이 변형을 사용하면 요청에 조건 표현식과 같은 더 많은 옵션을 추가할 수 있습니다. 다음 [예제](#ddb-en-client-use-multiop-trans-writeitems-opcondition)에서는 개별 작업에 대한 조건식을 보여줍니다.

주석 줄 3 다음에 업데이트 작업이 요청됩니다. `[TransactUpdateItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactUpdateItemEnhancedRequest.Builder.html)`에는 SDK가 모델 객체의 `null` 값으로 수행하는 작업을 구성할 수 있는 `ignoreNulls()` 메서드가 있습니다. `ignoreNulls()` 메서드가 true를 반환하는 경우 SDK는 `null`인 데이터 객체 속성에 대한 테이블의 속성 값을 제거하지 않습니다. `ignoreNulls()` 메서드가 false를 반환하면 SDK는 DynamoDB 서비스에 테이블의 항목에서 속성을 제거하도록 요청합니다. `ignoreNulls`의 기본값은 false입니다.

주석 줄 4 다음의 명령문은 데이터 객체를 취하는 삭제 요청의 변형을 보여줍니다. 향상된 클라이언트는 최종 요청을 발송하기 전에 키 값을 추출합니다.

```
    public static void transactWriteItems(DynamoDbEnhancedClient enhancedClient,
                                          DynamoDbTable<ProductCatalog> catalogTable,
                                          DynamoDbTable<MovieActor> movieActorTable) {

        enhancedClient.transactWriteItems(b -> b
                // 1. Simplest variation of put item request.
                .addPutItem(catalogTable, getProductCatId2())
                // 2. Put item request variation that accommodates condition expressions.
                .addPutItem(movieActorTable, TransactPutItemEnhancedRequest.builder(MovieActor.class)
                        .item(getMovieActorStreep())
                        .conditionExpression(Expression.builder().expression("attribute_not_exists (movie)").build())
                        .build())
                // 3. Update request that does not remove attribute values on the table if the data object's value is null.
                .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class)
                        .item(getProductCatId4ForUpdate())
                        .ignoreNulls(Boolean.TRUE)
                        .build())
                // 4. Variation of delete request that accepts a data object. The key values are extracted for the request.
                .addDeleteItem(movieActorTable, getMovieActorBlanchett())
        );
    }
```

다음 도우미 메서드는 `add*Item` 매개 변수에 대한 데이터 개체를 제공합니다.

#### 도우미 메서드
<a name="ddb-en-client-use-multiop-trans-writeitems-basic-helpers"></a>

```
    public static ProductCatalog getProductCatId2() {
        return ProductCatalog.builder()
                .id(2)
                .isbn("1-565-85698")
                .authors(new HashSet<>(Arrays.asList("a", "b")))
                .price(BigDecimal.valueOf(30.22))
                .title("Title 55")
                .build();
    }

    public static ProductCatalog getProductCatId4ForUpdate() {
        return ProductCatalog.builder()
                .id(4)
                .price(BigDecimal.valueOf(40.00))
                .title("Title 1")
                .build();
    }

    public static MovieActor getMovieActorBlanchett() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Cate Blanchett");
        movieActor.setMovieName("Tar");
        movieActor.setActingYear(2022);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("National Institute of Dramatic Art");
        return movieActor;
    }

    public static MovieActor getMovieActorStreep() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Meryl Streep");
        movieActor.setMovieName("Sophie's Choice");
        movieActor.setActingYear(1982);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("Yale School of Drama");
        return movieActor;
    }
```

코드 예제가 실행되기 전에 DynamoDB 테이블에는 다음 항목이 포함되어 있습니다.

```
1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2 | MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}
```

코드 실행이 완료된 후 테이블에 다음 항목이 표시됩니다.

```
3 | ProductCatalog{id=2, title='Title 55', isbn='1-565-85698', authors=[a, b], price=30.22}
4 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=40.0}
5 | MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
```

2행 항목은 삭제되었으며, 3행과 5행에는 추가된 항목이 표시됩니다. 4행에는 라인 1의 업데이트 내용이 표시됩니다. `price` 값은 항목에서 변경된 유일한 값입니다. `ignoreNulls()`가 false를 반환했다면 4행은 다음 줄처럼 보일 것입니다.

```
ProductCatalog{id=4, title='Title 1', isbn='null', authors=null, price=40.0}
```

### 조건 검사 예제
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond"></a>

다음 예에서는 조건 검사의 사용을 보여줍니다. 조건 검사는 항목이 있는지 확인하거나 데이터베이스의 항목의 특정 속성에 대한 조건을 검사하는 데 사용됩니다. 조건 확인에서 확인한 항목은 해당 거래의 다른 작업에 사용할 수 없습니다.

**참고**  
동일한 트랜잭션 내에서 동일한 항목을 여러 작업의 대상으로 지정할 수 없습니다. 예를 들어 동일한 트랜잭션에서 조건 확인과 동일한 항목 업데이트를 동시에 수행할 수 없습니다.

이 예에서는 트랜잭션 쓰기 항목 요청의 각 작업 유형 중 하나를 보여줍니다. `addConditionCheck()` 메서드는 주석 줄 2 다음에 `conditionExpression` 매개 변수가 `false`로 평가될 경우 트랜잭션을 실패시키는 조건을 제공합니다. Helper 메서드 블록에 표시된 메서드에서 반환되는 조건 표현식은 영화 `Sophie's Choice`의 수상 연도가 `1982`과 같지 않은지 확인합니다. 값이 맞으면 표현식이 `false`까지 평가되고 트랜잭션이 실패합니다.

이 가이드에서는 다른 항목에서 [표현식](ddb-en-client-expressions.md)에 대해 자세히 설명합니다.

```
    public static void conditionCheckFailExample(DynamoDbEnhancedClient enhancedClient,
                                                 DynamoDbTable<ProductCatalog> catalogTable,
                                                 DynamoDbTable<MovieActor> movieActorTable) {

        try {
            enhancedClient.transactWriteItems(b -> b
                    // 1. Perform one of each type of operation with the next three methods.
                    .addPutItem(catalogTable, TransactPutItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId2()).build())
                    .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId4ForUpdate())
                            .ignoreNulls(Boolean.TRUE).build())
                    .addDeleteItem(movieActorTable, TransactDeleteItemEnhancedRequest.builder()
                            .key(b1 -> b1
                                    .partitionValue(getMovieActorBlanchett().getMovieName())
                                    .sortValue(getMovieActorBlanchett().getActorName())).build())
                    // 2. Add a condition check on a table item that is not involved in another operation in this request.
                    .addConditionCheck(movieActorTable, ConditionCheck.builder()
                            .conditionExpression(buildConditionCheckExpression())
                            .key(k -> k
                                    .partitionValue("Sophie's Choice")
                                    .sortValue("Meryl Streep"))
                            // 3. Specify the request to return existing values from the item if the condition evaluates to true.
                            .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
                            .build())
                    .build());
        // 4. Catch the exception if the transaction fails and log the information.
        } catch (TransactionCanceledException ex) {
            ex.cancellationReasons().stream().forEach(cancellationReason -> {
                logger.info(cancellationReason.toString());
            });
        }
    }
```

이전 코드 예제에서는 다음과 같은 도우미 메서드를 사용했습니다.

#### 도우미 메서드
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond-helpers"></a>

```
    private static Expression buildConditionCheckExpression() {
        Map<String, AttributeValue> expressionValue = Map.of(
                ":year", numberValue(1982));

        return Expression.builder()
                .expression("actingyear <> :year")
                .expressionValues(expressionValue)
                .build();
    }

    public static ProductCatalog getProductCatId2() {
        return ProductCatalog.builder()
                .id(2)
                .isbn("1-565-85698")
                .authors(new HashSet<>(Arrays.asList("a", "b")))
                .price(BigDecimal.valueOf(30.22))
                .title("Title 55")
                .build();
    }

    public static ProductCatalog getProductCatId4ForUpdate() {
        return ProductCatalog.builder()
                .id(4)
                .price(BigDecimal.valueOf(40.00))
                .title("Title 1")
                .build();
    }

    public static MovieActor getMovieActorBlanchett() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Cate Blanchett");
        movieActor.setMovieName("Blue Jasmine");
        movieActor.setActingYear(2013);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("National Institute of Dramatic Art");
        return movieActor;
    }
```

코드 예제가 실행되기 전에 DynamoDB 테이블에는 다음 항목이 포함되어 있습니다.

```
1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2 | MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
3 | MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}
```

코드 실행이 완료된 후 테이블에 다음 항목이 표시됩니다.

```
ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}
```

트랜잭션이 실패했기 때문에 테이블의 항목은 변경되지 않은 상태로 유지됩니다. 동영상 `Sophie's Choice`의 `actingYear` 값은 `1982`으로, `transactWriteItem()` 메서드가 호출되기 전 테이블 항목의 2행에 표시된 것과 같습니다.

트랜잭션에 대한 취소 정보를 캡처하려면 `transactWriteItems()` 메서드 호출을 `try` 블록으로 묶고 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html)를 `catch`합니다. 예제의 주석 줄 4 다음에 코드가 각 `[CancellationReason](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CancellationReason.html)` 객체를 기록합니다. 예제의 주석 줄 3 뒤에 오는 코드에서는 트랜잭션 실패를 초래한 항목에 대해 값을 반환하도록 지정하기 때문에 로그에는 `Sophie's Choice` 영화 항목에 대한 원시 데이터베이스 값이 표시됩니다.

```
CancellationReason(Code=None)
CancellationReason(Code=None)
CancellationReason(Code=None)
CancellationReason(Item={actor=AttributeValue(S=Meryl Streep), movie=AttributeValue(S=Sophie's Choice), actingaward=AttributeValue(S=Best Actress), actingyear=AttributeValue(N=1982), actingschoolname=AttributeValue(S=Yale School of Drama)}, ¬
    Code=ConditionalCheckFailed, Message=The conditional request failed.)
```

### 단일 작업 조건 예제
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition"></a>

다음 예제는 트랜잭션 요청의 단일 작업에 대한 조건 사용을 보여줍니다. 주석 라인 1 이후의 삭제 작업에는 데이터베이스에 대해 작업의 대상 항목 값을 확인하는 조건이 포함되어 있습니다. 이 예제에서 주석 행 2 다음에 도우미 메서드를 사용하여 생성된 조건식은 영화의 개봉 연도가 2013년이 아닌 경우 데이터베이스에서 항목을 삭제해야 함을 지정합니다.

[표현식](ddb-en-client-expressions.md)은 이 가이드의 뒷부분에서 설명합니다.

```
    public static void singleOperationConditionFailExample(DynamoDbEnhancedClient enhancedClient,
                                                           DynamoDbTable<ProductCatalog> catalogTable,
                                                           DynamoDbTable<MovieActor> movieActorTable) {
        try {
            enhancedClient.transactWriteItems(b -> b
                    .addPutItem(catalogTable, TransactPutItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId2())
                            .build())
                    .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId4ForUpdate())
                            .ignoreNulls(Boolean.TRUE).build())
                    // 1. Delete operation that contains a condition expression
                    .addDeleteItem(movieActorTable, TransactDeleteItemEnhancedRequest.builder()
                            .key((Key.Builder k) -> {
                                MovieActor blanchett = getMovieActorBlanchett();
                                k.partitionValue(blanchett.getMovieName())
                                        .sortValue(blanchett.getActorName());
                            })
                            .conditionExpression(buildDeleteItemExpression())
                            .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
                            .build())
                    .build());
        } catch (TransactionCanceledException ex) {
            ex.cancellationReasons().forEach(cancellationReason -> logger.info(cancellationReason.toString()));
        }
    }

    // 2. Provide condition expression to check if 'actingyear' is not equal to 2013.
    private static Expression buildDeleteItemExpression() {
        Map<String, AttributeValue> expressionValue = Map.of(
                ":year", numberValue(2013));

        return Expression.builder()
                .expression("actingyear <> :year")
                .expressionValues(expressionValue)
                .build();
    }
```

이전 코드 예제에서는 다음과 같은 도우미 메서드를 사용했습니다.

#### 도우미 메서드
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition-helpers"></a>

```
    public static ProductCatalog getProductCatId2() {
        return ProductCatalog.builder()
                .id(2)
                .isbn("1-565-85698")
                .authors(new HashSet<>(Arrays.asList("a", "b")))
                .price(BigDecimal.valueOf(30.22))
                .title("Title 55")
                .build();
    }

    public static ProductCatalog getProductCatId4ForUpdate() {
        return ProductCatalog.builder()
                .id(4)
                .price(BigDecimal.valueOf(40.00))
                .title("Title 1")
                .build();
    }
    public static MovieActor getMovieActorBlanchett() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Cate Blanchett");
        movieActor.setMovieName("Blue Jasmine");
        movieActor.setActingYear(2013);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("National Institute of Dramatic Art");
        return movieActor;
    }
```

코드 예제가 실행되기 전에 DynamoDB 테이블에는 다음 항목이 포함되어 있습니다.

```
1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2 | MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}
```

코드 실행이 완료된 후 테이블에 다음 항목이 표시됩니다.

```
ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2023-03-15 11:29:07 [main] INFO  org.example.tests.TransactDemoTest:168 - MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}
```

트랜잭션이 실패했기 때문에 테이블의 항목은 변경되지 않은 상태로 유지됩니다. 영화 `Blue Jasmine`의 `actingYear` 값은 코드 예제가 실행되기 전 항목 목록의 2행에 표시된 것과 같은 `2013`입니다.

다음 줄이 콘솔에 기록됩니다.

```
CancellationReason(Code=None)
CancellationReason(Code=None)
CancellationReason(Item={actor=AttributeValue(S=Cate Blanchett), movie=AttributeValue(S=Blue Jasmine), actingaward=AttributeValue(S=Best Actress), actingyear=AttributeValue(N=2013), actingschoolname=AttributeValue(S=National Institute of Dramatic Art)}, 
    Code=ConditionalCheckFailed, Message=The conditional request failed)
```

# 보조 인덱스 사용
<a name="ddb-en-client-use-secindex"></a>

보조 인덱스는 쿼리 및 스캔 작업에 사용하는 대체 키를 정의하여 데이터 액세스를 향상시킵니다. GSI(글로벌 보조 인덱스)에는 기본 테이블의 파티션 키와 정렬 키가 다를 수 있습니다. 반대로 LSI(로컬 보조 인덱스)는 기본 인덱스의 파티션 키를 사용합니다.

## 보조 인덱스 주석으로 데이터 클래스에 주석 달기
<a name="ddb-en-client-use-secindex-annomodel"></a>

보조 인덱스에 속하는 속성에는 `@DynamoDbSecondaryPartitionKey` 또는 `@DynamoDbSecondarySortKey` 주석이 필요합니다.

다음 클래스는 두 인덱스에 대한 주석을 보여줍니다. *SubjectLastPostedDateIndex*라는 GSI는 파티션 키에 `Subject` 속성을 사용하고 정렬 키에는 `LastPostedDateTime` 속성을 사용합니다. *ForumLastPostedDateIndex라는* 이름의 LSI는 `ForumName`를 파티션 키로, `LastPostedDateTime`를 정렬 키로 사용합니다.

`Subject` 속성은 이중 역할을 한다는 점에 유의하세요. 기본 키의 정렬 키이자 *SubjectLastPostedDateIndex*라는 GSI의 파티션 키입니다.

### `MessageThread` 클래스
<a name="ddb-en-client-use-secindex-class"></a>

이 `MessageThread` 클래스는 *Amazon DynamoDB 개발자 안내서*의 [예제 스레드 테이블](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AppendixSampleTables.html)의 데이터 클래스로 사용하기에 적합합니다.

#### 가져오기
<a name="ddb-en-client-use-secindex-classimports"></a>

```
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
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.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.util.List;
```

```
@DynamoDbBean
public class MessageThread {
    private String ForumName;
    private String Subject;
    private String Message;
    private String LastPostedBy;
    private String LastPostedDateTime;
    private Integer Views;
    private Integer Replies;
    private Integer Answered;
    private List<String> Tags;

    @DynamoDbPartitionKey
    public String getForumName() {
        return ForumName;
    }

    public void setForumName(String forumName) {
        ForumName = forumName;
    }

    // Sort key for primary index and partition key for GSI "SubjectLastPostedDateIndex".
    @DynamoDbSortKey
    @DynamoDbSecondaryPartitionKey(indexNames = "SubjectLastPostedDateIndex")
    public String getSubject() {
        return Subject;
    }

    public void setSubject(String subject) {
        Subject = subject;
    }

    // Sort key for GSI "SubjectLastPostedDateIndex" and sort key for LSI "ForumLastPostedDateIndex".
    @DynamoDbSecondarySortKey(indexNames = {"SubjectLastPostedDateIndex", "ForumLastPostedDateIndex"})
    public String getLastPostedDateTime() {
        return LastPostedDateTime;
    }

    public void setLastPostedDateTime(String lastPostedDateTime) {
        LastPostedDateTime = lastPostedDateTime;
    }
    public String getMessage() {
        return Message;
    }

    public void setMessage(String message) {
        Message = message;
    }

    public String getLastPostedBy() {
        return LastPostedBy;
    }

    public void setLastPostedBy(String lastPostedBy) {
        LastPostedBy = lastPostedBy;
    }

    public Integer getViews() {
        return Views;
    }

    public void setViews(Integer views) {
        Views = views;
    }

    @DynamoDbSecondaryPartitionKey(indexNames = "ForumRepliesIndex")
    public Integer getReplies() {
        return Replies;
    }

    public void setReplies(Integer replies) {
        Replies = replies;
    }

    public Integer getAnswered() {
        return Answered;
    }

    public void setAnswered(Integer answered) {
        Answered = answered;
    }

    public List<String> getTags() {
        return Tags;
    }

    public void setTags(List<String> tags) {
        Tags = tags;
    }

    public MessageThread() {
        this.Answered = 0;
        this.LastPostedBy = "";
        this.ForumName = "";
        this.Message = "";
        this.LastPostedDateTime = "";
        this.Replies = 0;
        this.Views = 0;
        this.Subject = "";
    }

    @Override
    public String toString() {
        return "MessageThread{" +
                "ForumName='" + ForumName + '\'' +
                ", Subject='" + Subject + '\'' +
                ", Message='" + Message + '\'' +
                ", LastPostedBy='" + LastPostedBy + '\'' +
                ", LastPostedDateTime='" + LastPostedDateTime + '\'' +
                ", Views=" + Views +
                ", Replies=" + Replies +
                ", Answered=" + Answered +
                ", Tags=" + Tags +
                '}';
    }
}
```

## 인덱스 만들기
<a name="ddb-en-client-use-secindex-confindex"></a>

Java용 SDK 버전 2.20.86부터 이 `createTable()` 메서드는 데이터 클래스 주석에서 보조 인덱스를 자동으로 생성합니다. 기본적으로 기본 테이블의 모든 속성이 인덱스에 복사되며 프로비저닝된 처리량 값은 20 읽기 용량 단위 및 20 쓰기 용량 단위입니다.

단, SDK 2.20.86 이전 버전을 사용하는 경우에는 다음 예와 같이 테이블과 함께 인덱스를 빌드해야 합니다. 이 예제에서는 `Thread` 테이블의 인덱스 두 개를 빌드합니다. [빌더](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html) 매개 변수에는 주석 줄 1과 2 다음에 표시된 것처럼 두 가지 유형의 인덱스를 모두 구성하는 메서드가 있습니다. 인덱스 빌더의 `indexName()` 메서드를 사용하여 데이터 클래스 주석에 지정된 인덱스 이름을 의도한 인덱스 유형과 연결할 수 있습니다.

이 코드는 모든 테이블 속성이 주석 라인 3과 4 다음에 있는 두 인덱스 모두에 포함되도록 구성합니다. [속성 프로젝션](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.Projections)에 대한 자세한 내용은 *Amazon DynamoDB 개발자 안내서*에서 확인할 수 있습니다.

```
    public static void createMessageThreadTable(DynamoDbTable<MessageThread> messageThreadDynamoDbTable, DynamoDbClient dynamoDbClient) {
        messageThreadDynamoDbTable.createTable(b -> b
                // 1. Generate the GSI.
                .globalSecondaryIndices(gsi -> gsi.indexName("SubjectLastPostedDateIndex")
                        // 3. Populate the GSI with all attributes.
                        .projection(p -> p
                                .projectionType(ProjectionType.ALL))
                )
                // 2. Generate the LSI.
                .localSecondaryIndices(lsi -> lsi.indexName("ForumLastPostedDateIndex")
                        // 4. Populate the LSI with all attributes.
                        .projection(p -> p
                                .projectionType(ProjectionType.ALL))
                )
        );
```

## 인덱스를 사용하여 쿼리
<a name="ddb-en-client-use-secindex-query"></a>

다음 예시에서는 로컬 보조 인덱스 *ForumLastPostedDateIndex*를 쿼리합니다.

주석 줄 2에 이어 [DynamoDbIndex.query()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbIndex.html#query(java.util.function.Consumer)) 메서드를 호출할 때 필요한 [QueryConditional](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html) 객체를 생성합니다.

인덱스 이름을 전달하여 주석 줄 3 다음에 쿼리하려는 인덱스에 대한 참조를 얻습니다. 주석 줄 4에 이어 `QueryConditional` 객체를 전달하는 인덱스에서 `query()` 메서드를 호출합니다.

또한 주석 줄 5 다음에 표시된 대로 세 가지 속성 값을 반환하도록 쿼리를 구성합니다. `attributesToProject()`가 호출되지 않은 경우 쿼리는 모든 속성 값을 반환합니다. 지정된 속성 이름은 소문자로 시작하는 것을 알 수 있습니다. 이러한 속성 이름은 테이블에 사용된 이름과 일치하지만 반드시 데이터 클래스의 속성 이름과 일치할 필요는 없습니다.

주석 줄 6 다음에 결과를 반복하고 쿼리에서 반환된 각 항목을 기록하고 목록에 저장하여 호출자에게 반환합니다.

```
public class IndexScanExamples {
    private static Logger logger = LoggerFactory.getLogger(IndexScanExamples.class);

    public static List<MessageThread> queryUsingSecondaryIndices(String lastPostedDate,
                                                                 DynamoDbTable<MessageThread> threadTable) {
        // 1. Log the parameter value.
        logger.info("lastPostedDate value: {}", lastPostedDate);

        // 2. Create a QueryConditional whose sort key value must be greater than or equal to the parameter value.
        QueryConditional queryConditional = QueryConditional.sortGreaterThanOrEqualTo(qc ->
                qc.partitionValue("Forum02").sortValue(lastPostedDate));

        // 3. Specify the index name to query.
        final DynamoDbIndex<MessageThread> forumLastPostedDateIndex = threadTable.index("ForumLastPostedDateIndex");

        // 4. Perform the query using the QueryConditional object.
        final SdkIterable<Page<MessageThread>> pagedResult = forumLastPostedDateIndex.query(q -> q
                .queryConditional(queryConditional)
                // 5. Request three attribute in the results.
                .attributesToProject("forumName", "subject", "lastPostedDateTime"));

        List<MessageThread> collectedItems = new ArrayList<>();
        // 6. Iterate through pages response and sort the items.
        pagedResult.stream().forEach(page -> page.items().stream()
                .sorted(Comparator.comparing(MessageThread::getLastPostedDateTime))
                .forEach(mt -> {
                    // 7. Log the returned items and add the collection to return to the caller.
                    logger.info(mt.toString());
                    collectedItems.add(mt);
                }));
        return collectedItems;
    }
```

쿼리가 실행되기 전의 데이터베이스에는 다음과 같은 항목이 있습니다.

```
MessageThread{ForumName='Forum01', Subject='Subject01', Message='Message01', LastPostedBy='', LastPostedDateTime='2023.03.28', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject02', Message='Message02', LastPostedBy='', LastPostedDateTime='2023.03.29', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject04', Message='Message04', LastPostedBy='', LastPostedDateTime='2023.03.31', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject08', Message='Message08', LastPostedBy='', LastPostedDateTime='2023.04.04', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject10', Message='Message10', LastPostedBy='', LastPostedDateTime='2023.04.06', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum03', Subject='Subject03', Message='Message03', LastPostedBy='', LastPostedDateTime='2023.03.30', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum03', Subject='Subject06', Message='Message06', LastPostedBy='', LastPostedDateTime='2023.04.02', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum03', Subject='Subject09', Message='Message09', LastPostedBy='', LastPostedDateTime='2023.04.05', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum05', Subject='Subject05', Message='Message05', LastPostedBy='', LastPostedDateTime='2023.04.01', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum07', Subject='Subject07', Message='Message07', LastPostedBy='', LastPostedDateTime='2023.04.03', Views=0, Replies=0, Answered=0, Tags=null}
```

1행과 6행의 로깅 명령문을 실행하면 다음과 같은 콘솔 출력이 나타납니다.

```
lastPostedDate value: 2023.03.31
MessageThread{ForumName='Forum02', Subject='Subject04', Message='', LastPostedBy='', LastPostedDateTime='2023.03.31', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject08', Message='', LastPostedBy='', LastPostedDateTime='2023.04.04', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject10', Message='', LastPostedBy='', LastPostedDateTime='2023.04.06', Views=0, Replies=0, Answered=0, Tags=null}
```

쿼리가 *Forum02*의 `forumName` 값과 *2023.03.31*보다 크거나 같은 `lastPostedDateTime`을 반환했습니다. 인덱스에 `message` 속성 값이 있더라도 결과에는 빈 문자열이 있는 `message` 값이 표시됩니다. 이는 주석 줄 5 이후에 코드로 메시지 속성이 프로젝션되지 않았기 때문입니다.

# 고급 매핑 기능 사용
<a name="ddb-en-client-adv-features"></a>

DynamoDB 향상된 클라이언트 API의 고급 테이블 스키마 기능에 대해 알아보세요.

## 테이블 스키마 유형 이해
<a name="ddb-en-client-adv-features-schm-overview"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)`는 DynamoDB 향상된 클라이언트 API의 매핑 기능에 대한 인터페이스입니다. [AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) 맵에 데이터 객체를 매핑하거나 이 맵에서 데이터 객체를 매핑할 수 있습니다. `TableSchema` 객체는 매핑하려는 테이블의 구조를 알아야 합니다. 이 구조 정보는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html) 객체에 저장됩니다.

향상된 클라이언트 API에는 `TableSchema`의 여러 구현이 있습니다.

### 주석이 달린 클래스에서 생성된 테이블 스키마
<a name="ddb-en-client-adv-features-schema-mapped"></a>

주석이 달린 클래스로 `TableSchema`를 빌드하는 작업은 비용이 다소 많이 들기 때문에 애플리케이션을 시작할 때 한 번 수행하는 것이 좋습니다.

 [BeanTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.html)   
이 구현은 Bean 클래스의 속성 및 주석을 기반으로 빌드됩니다. 이 접근 방식의 예제는 [시작하기 단원](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)에 나와 있습니다.  
`BeanTableSchema`가 예상대로 작동하지 않는 경우 `software.amazon.awssdk.enhanced.dynamodb.beans`에 대한 디버그 로깅을 활성화하세요.

[ImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/ImmutableTableSchema.html)  
이 구현은 변경할 수 없는 데이터 클래스를 기반으로 구축되었습니다. 이 접근은 [변경할 수 없는 데이터 클래스로 작업](ddb-en-client-use-immut.md) 단원에서 설명합니다.

### 빌더로 생성된 테이블 스키마
<a name="ddb-en-client-adv-features-schema-static"></a>

다음 `TableSchema`은 빌더를 사용하여 코드로 빌드됩니다. 이 접근 방식은 주석이 달린 데이터 클래스를 사용하는 접근 방식보다 비용이 저렴합니다. 빌더 접근 방식은 주석을 사용하지 않으므로 JavaBean 이름 지정 표준이 필요하지 않습니다.

[StaticTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.html)  
이 구현은 변경 가능한 데이터 클래스용으로 구축되었습니다. 이 가이드의 시작하기 단원에서는 [빌더를 사용하여 `StaticTableSchema`를 생성하는](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder) 방법을 보여 주었습니다.

[StaticImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)  
`StaticTableSchema`를 빌드하는 방법과 마찬가지로 변경할 수 없는 데이터 클래스와 함께 사용할 [빌더](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)를 사용하여 이러한 유형의 `TableSchema` 구현을 생성합니다.

### 고정 스키마가 없는 데이터에 대한 테이블 스키마
<a name="ddb-en-client-adv-features-schema-document"></a>

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)  
`TableSchema`의 다른 구현과 달리 `DocumentTableSchema` 인스턴스의 속성은 정의하지 않습니다. 일반적으로 기본 키와 특성 변환기 공급자만 지정합니다. `EnhancedDocument` 인스턴스는 개별 요소 또는 JSON 문자열에서 빌드한 속성을 제공합니다.

# 속성을 명시적으로 포함하거나 제외
<a name="ddb-en-client-adv-features-inex-attr"></a>

DynamoDB 향상된 클라이언트 API는 데이터 클래스 속성이 테이블의 속성이 되지 않도록 제외하는 주석을 제공합니다. API를 사용하면 데이터 클래스 속성 이름과 다른 속성 이름을 사용할 수도 있습니다.

## 속성 제외
<a name="ddb-en-client-adv-features-inex-attr-ex"></a>

DynamoDB 테이블에 매핑해서는 안 되는 속성을 무시하려면 속성을 `@DynamoDbIgnore` 주석으로 표시하세요.

```
private String internalKey;

@DynamoDbIgnore
public String getInternalKey() { return this.internalKey; }
public void setInternalKey(String internalKey) { this.internalKey = internalKey;}
```

## 속성 포함
<a name="ddb-en-client-adv-features-inex-attr-in"></a>

DynamoDB 테이블에서 사용되는 속성의 이름을 변경하려면 `@DynamoDbAttribute` 주석으로 표시하고 다른 이름을 제공하세요.

```
private String internalKey;

@DynamoDbAttribute("renamedInternalKey")
public String getInternalKey() { return this.internalKey; }
public void setInternalKey(String internalKey) { this.internalKey = internalKey;}
```

# 제어 속성 변환
<a name="ddb-en-client-adv-features-conversion"></a>

기본적으로 테이블 스키마는 `[AttributeConverterProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.html)` 인터페이스의 기본 구현을 통해 많은 공통 Java 유형에 대한 변환기를 제공합니다. 사용자 지정 `AttributeConverterProvider` 구현으로 전체 기본 동작을 변경할 수 있습니다. 단일 속성의 변환기를 변경할 수도 있습니다.

사용 가능한 컨버터 목록은 [AttributeConverter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html) 인터페이스 Java 문서를 참조하세요.

## 사용자 정의 속성 변환기 공급자 제공
<a name="ddb-en-client-adv-features-conversion-prov"></a>

`@DynamoDbBean` `(converterProviders = {…})`주석을 통해 정렬된 단일 `AttributeConverterProvider` 또는 순서로 정리된 `AttributeConverterProvider`의 체인을 제공할 수 있습니다. 모든 사용자 지정 `AttributeConverterProvider`은 `AttributeConverterProvider` 인터페이스를 확장해야 합니다.

자체 특성 변환기 공급자 체인을 제공하는 경우 기본 변환기 공급자 `DefaultAttributeConverterProvider`가 재정의됨을 유의하세요. `DefaultAttributeConverterProvider`의 기능을 사용하려면 이 기능을 체인에 포함해야 합니다.

빈 배열 `{}`로 Bean에 주석을 달 수도 있습니다. 이렇게 하면 기본값을 포함하여 모든 특성 변환기 공급자의 사용이 비활성화됩니다. 이 경우 매핑할 모든 속성에는 고유한 속성 변환기가 있어야 합니다.

다음 코드 조각은 단일 변환기 제공자를 보여줍니다.

```
@DynamoDbBean(converterProviders = ConverterProvider1.class)
public class Customer {

}
```

다음 코드 조각은 변환기 제공자 체인의 사용을 보여줍니다. SDK 기본값은 마지막에 제공되므로 우선순위가 가장 낮습니다.

```
@DynamoDbBean(converterProviders = {
   ConverterProvider1.class, 
   ConverterProvider2.class,
   DefaultAttributeConverterProvider.class})
public class Customer {

}
```

정적 테이블 스키마 빌더에는 동일한 `attributeConverterProviders()` 방식으로 작동하는 메서드가 있습니다. 이는 다음 예제와 같습니다.

```
private static final StaticTableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
  StaticTableSchema.builder(Customer.class)
    .newItemSupplier(Customer::new)
    .addAttribute(String.class, a -> a.name("name")
                                     a.getter(Customer::getName)
                                     a.setter(Customer::setName))
    .attributeConverterProviders(converterProvider1, converterProvider2)
    .build();
```

## 단일 속성의 매핑 재정의
<a name="ddb-en-client-adv-features-conversion-single"></a>

단일 속성이 매핑되는 방식을 재정의하려면 속성에 `AttributeConverter`을 제공하세요. 이 추가 기능은 테이블 스키마의 `AttributeConverterProviders`에서 제공하는 모든 변환기를 재정의합니다. 이렇게 하면 해당 속성에만 사용자 지정 변환기가 추가됩니다. 동일한 유형의 속성이라 할지라도, 다른 속성은 해당 속성에 대해 명시적으로 지정되지 않는 한 해당 변환기를 사용하지 않습니다.

`@DynamoDbConvertedBy` 주석은 다음 코드 조각과 같이 사용자 지정 `AttributeConverter` 클래스를 지정하는 데 사용됩니다.

```
@DynamoDbBean
public class Customer {
    private String name;

    @DynamoDbConvertedBy(CustomAttributeConverter.class)
    public String getName() { return this.name; }
    public void setName(String name) { this.name = name;}
}
```

정적 스키마용 빌더에는 동일한 속성 빌더 `attributeConverter()` 메서드가 있습니다. 이 메서드는 다음과 같이 `AttributeConverter`의 인스턴스를 사용합니다.

```
private static final StaticTableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
  StaticTableSchema.builder(Customer.class)
    .newItemSupplier(Customer::new)
    .addAttribute(String.class, a -> a.name("name")
                                     a.getter(Customer::getName)
                                     a.setter(Customer::setName)
                                     a.attributeConverter(customAttributeConverter))
    .build();
```

## 예제
<a name="ddb-en-client-adv-features-conversion-example"></a>

이 예제는 [https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html) 객체의 속성 변환기를 제공하는 `AttributeConverterProvider` 구현을 보여줍니다.

다음 `SimpleUser` 클래스에는 `HttpCookie`의 인스턴스인 이름이 `lastUsedCookie`로 지정된 속성이 들어 있습니다.

`@DynamoDbBean` 주석의 매개 변수에는 변환기를 제공하는 두 `AttributeConverterProvider` 클래스가 나열되어 있습니다.

------
#### [ Class with annotations ]

```
    @DynamoDbBean(converterProviders = {CookieConverterProvider.class, DefaultAttributeConverterProvider.class})
    public static final class SimpleUser {
        private String name;
        private HttpCookie lastUsedCookie;

        @DynamoDbPartitionKey
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public HttpCookie getLastUsedCookie() {
            return lastUsedCookie;
        }

        public void setLastUsedCookie(HttpCookie lastUsedCookie) {
            this.lastUsedCookie = lastUsedCookie;
        }
```

------
#### [ Static table schema ]

```
    private static final TableSchema<SimpleUser> SIMPLE_USER_TABLE_SCHEMA =
            TableSchema.builder(SimpleUser.class)
                    .newItemSupplier(SimpleUser::new)
                    .attributeConverterProviders(CookieConverterProvider.create(), AttributeConverterProvider.defaultProvider())
                    .addAttribute(String.class, a -> a.name("name")
                            .setter(SimpleUser::setName)
                            .getter(SimpleUser::getName)
                            .tags(StaticAttributeTags.primaryPartitionKey()))
                    .addAttribute(HttpCookie.class, a -> a.name("lastUsedCookie")
                            .setter(SimpleUser::setLastUsedCookie)
                            .getter(SimpleUser::getLastUsedCookie))
                    .build();
```

------

다음 `CookieConverterProvider` 예제에서는 `HttpCookeConverter`의 인스턴스를 제공합니다.

```
    public static final class CookieConverterProvider implements AttributeConverterProvider {
        private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
                // 1. Add HttpCookieConverter to the internal cache.
                EnhancedType.of(HttpCookie.class), new HttpCookieConverter());

        public static CookieConverterProvider create() {
            return new CookieConverterProvider();
        }

        // The SDK calls this method to find out if the provider contains a AttributeConverter instance
        // for the EnhancedType<T> argument.
        @SuppressWarnings("unchecked")
        @Override
        public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
            return (AttributeConverter<T>) converterCache.get(enhancedType);
        }
    }
```

### 전환 코드
<a name="ddb-en-client-adv-features-conversion-example-code"></a>

다음 `HttpCookieConverter` 클래스의 `transformFrom()` 메서드에서 코드는 `HttpCookie` 인스턴스를 수신하여 속성으로 저장되는 DynamoDB 맵으로 변환합니다.

`transformTo()` 메서드는 DynamoDB 맵 파라미터를 수신한 다음 이름과 값이 필요한 생성자를 `HttpCookie` 호출합니다.

```
    public static final class HttpCookieConverter implements AttributeConverter<HttpCookie> {

        @Override
        public AttributeValue transformFrom(HttpCookie httpCookie) {

            return AttributeValue.fromM(
            Map.of ("cookieName", AttributeValue.fromS(httpCookie.getName()),
                    "cookieValue", AttributeValue.fromS(httpCookie.getValue()))
            );
        }

        @Override
        public HttpCookie transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> map = attributeValue.m();
            return new HttpCookie(
                    map.get("cookieName").s(),
                    map.get("cookieValue").s());
        }

        @Override
        public EnhancedType<HttpCookie> type() {
            return EnhancedType.of(HttpCookie.class);
        }

        @Override
        public AttributeValueType attributeValueType() {
            return AttributeValueType.M;
        }
    }
```

# 속성의 업데이트 동작 변경
<a name="ddb-en-client-adv-features-upd-behavior"></a>

*업데이트* 작업을 수행할 때 개별 속성의 업데이트 동작을 사용자 지정할 수 있습니다. DynamoDB 향상된 클라이언트 API의 업데이트 작업 예로는 [updateItem()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#updateItem(T)) 및 [transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))가 있습니다.

예를 들어 *생성된* 타임스탬프를 레코드에 저장하고 싶다고 가정해 보겠습니다. 그러나 데이터베이스에 이미 속성에 대한 기존 값이 없는 경우에만 해당 값이 기록되기를 원합니다. 이 경우에는 `[WRITE\$1IF\$1NOT\$1EXISTS](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html#WRITE_IF_NOT_EXISTS)` 업데이트 동작을 사용합니다.

다음 예제는 `createdOn` 속성에 동작을 추가하는 주석을 보여줍니다.

```
@DynamoDbBean
public class Customer extends GenericRecord {
    private String id;
    private Instant createdOn;

    @DynamoDbPartitionKey
    public String getId() { return this.id; }
    public void setId(String id) { this.name = id; }

    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public Instant getCreatedOn() { return this.createdOn; }    
    public void setCreatedOn(Instant createdOn) { this.createdOn = createdOn; }
}
```

주석 행 1 다음에 다음 예에 표시된 대로 정적 테이블 스키마를 빌드할 때 동일한 업데이트 동작을 선언할 수 있습니다.

```
static final TableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
     TableSchema.builder(Customer.class)
       .newItemSupplier(Customer::new)
       .addAttribute(String.class, a -> a.name("id")
                                         .getter(Customer::getId)
                                         .setter(Customer::setId)
                                         .tags(StaticAttributeTags.primaryPartitionKey()))
       .addAttribute(Instant.class, a -> a.name("createdOn")
                                          .getter(Customer::getCreatedOn)
                                          .setter(Customer::setCreatedOn)
                                          // 1. Add an UpdateBehavior.
                                          .tags(StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)))
       .build();
```

# 다른 클래스의 속성을 평면화
<a name="ddb-en-client-adv-features-flatmap"></a>

테이블 속성이 상속 또는 구성을 통해 여러 Java 클래스에 분산되어 있는 경우 DynamoDB 향상된 클라이언트 API는 속성을 하나의 클래스로 병합할 수 있도록 지원합니다.

## 상속 사용
<a name="ddb-en-client-adv-features-flatmap-inheritance"></a>

클래스에서 상속을 사용하는 경우 다음 방법을 사용하여 계층 구조를 평면화하세요.

### 주석이 달린 빈을 사용
<a name="ddb-en-client-adv-features-flatmap-inheritance-anno"></a>

주석 접근 방식의 경우 두 클래스 모두 `@DynamoDbBean` 주석을 포함해야 하며 클래스에는 기본 키 주석이 하나 이상 있어야 합니다.

다음은 상속 관계가 있는 데이터 클래스의 예를 보여줍니다.

------
#### [ Standard data class ]

```
@DynamoDbBean
public class Customer extends GenericRecord {
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

@DynamoDbBean
public abstract class GenericRecord {
    private String id;
    private String createdDate;

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

    public String getCreatedDate() { return createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

Lombok의 [`onMethod` 옵션](https://projectlombok.org/features/experimental/onX)은 속성 기반 DynamoDB 주석(예: `@DynamoDbPartitionKey`)을 생성된 코드에 복사합니다.

```
@DynamoDbBean
@Data
@ToString(callSuper = true)
public class Customer extends GenericRecord {
    private String name;
}

@Data
@DynamoDbBean
public abstract class GenericRecord {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;
    private String createdDate;
}
```

------

### 정적 스키마 사용
<a name="ddb-en-client-adv-features-flatmap-inheritance-static"></a>

정적 스키마 접근 방식의 경우 빌더의 `extend()` 메서드를 사용하여 부모 클래스의 속성을 자식 클래스로 축소합니다. 이는 다음 예에서 주석 라인 1 뒤에 표시됩니다.

```
        StaticTableSchema<org.example.tests.model.inheritance.stat.GenericRecord> GENERIC_RECORD_SCHEMA =
                StaticTableSchema.builder(org.example.tests.model.inheritance.stat.GenericRecord.class)
                        // The partition key will be inherited by the top level mapper.
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(org.example.tests.model.inheritance.stat.GenericRecord::getId)
                                .setter(org.example.tests.model.inheritance.stat.GenericRecord::setId)
                                .tags(primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("created_date")
                                .getter(org.example.tests.model.inheritance.stat.GenericRecord::getCreatedDate)
                                .setter(org.example.tests.model.inheritance.stat.GenericRecord::setCreatedDate))
                        .build();

        StaticTableSchema<org.example.tests.model.inheritance.stat.Customer> CUSTOMER_SCHEMA =
                StaticTableSchema.builder(org.example.tests.model.inheritance.stat.Customer.class)
                        .newItemSupplier(org.example.tests.model.inheritance.stat.Customer::new)
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(org.example.tests.model.inheritance.stat.Customer::getName)
                                .setter(org.example.tests.model.inheritance.stat.Customer::setName))
                        // 1. Use the extend() method to collapse the parent attributes onto the child class.
                        .extend(GENERIC_RECORD_SCHEMA)     // All the attributes of the GenericRecord schema are added to Customer.
                        .build();
```

이전 정적 스키마 예제는 다음의 데이터 클래스를 사용합니다. 정적 테이블 스키마를 작성할 때 매핑이 정의되므로 데이터 클래스에는 주석이 필요하지 않습니다.

#### 데이터 클래스
<a name="gunk"></a>

------
#### [ Standard data class ]

```
public class Customer extends GenericRecord {
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}


public abstract class GenericRecord {
    private String id;
    private String createdDate;

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getCreatedDate() { return createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
```

------
#### [ Lombok ]

```
@Data
@ToString(callSuper = true)
public class Customer extends GenericRecord{
    private String name;
}

@Data
public abstract class GenericRecord {
    private String id;
    private String createdDate;
}
```

------

## 구성 사용
<a name="ddb-en-client-adv-features-flatmap-comp"></a>

클래스에서 컴포지션을 사용하는 경우 다음 방법을 사용하여 계층 구조를 평면화하세요.

### 주석이 달린 빈을 사용
<a name="ddb-en-client-adv-features-flatmap-comp-anno"></a>

`@DynamoDbFlatten` 주석은 포함된 클래스를 평면화합니다.

다음 데이터 클래스 예제는 `@DynamoDbFlatten` 주석을 사용하여 포함된 `GenericRecord` 클래스의 모든 속성을 `Customer` 클래스에 효과적으로 추가합니다.

------
#### [ Standard data class ]

```
@DynamoDbBean
public class Customer {
    private String name;
    private GenericRecord record;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    @DynamoDbFlatten
    public GenericRecord getRecord() { return this.record; }
    public void setRecord(GenericRecord record) { this.record = record; }

@DynamoDbBean
public class GenericRecord {
    private String id;
    private String createdDate;

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

    public String getCreatedDate() { return this.createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

```
@Data
@DynamoDbBean
public class Customer {
    private String name;
    @Getter(onMethod_=@DynamoDbFlatten)
    private GenericRecord record;
}

@Data
@DynamoDbBean
public class GenericRecord {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;
    private String createdDate;
}
```

------

평면화 주석을 사용하여 필요한 만큼 다양한 적격 클래스를 병합할 수 있습니다. 다음과 같은 제약이 적용됩니다.
+ 병합한 후에는 모든 속성 이름이 고유해야 합니다.
+ 파티션 키, 정렬 키 또는 테이블 이름이 두 개를 초과해서는 안 됩니다.

### 정적 스키마 사용
<a name="ddb-en-client-adv-features-flatmap-comp-static"></a>

정적 테이블 스키마를 빌드할 때는 빌더의 `flatten()` 메서드를 사용하세요. 포함된 클래스를 식별하는 접근자 및 설정자 메서드도 제공합니다.

```
        StaticTableSchema<GenericRecord> GENERIC_RECORD_SCHEMA =
                StaticTableSchema.builder(GenericRecord.class)
                        .newItemSupplier(GenericRecord::new)
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(GenericRecord::getId)
                                .setter(GenericRecord::setId)
                                .tags(primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("created_date")
                                .getter(GenericRecord::getCreatedDate)
                                .setter(GenericRecord::setCreatedDate))
                        .build();

        StaticTableSchema<Customer> CUSTOMER_SCHEMA =
                StaticTableSchema.builder(Customer.class)
                        .newItemSupplier(Customer::new)
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(Customer::getName)
                                .setter(Customer::setName))
                        // Because we are flattening a component object, we supply a getter and setter so the
                        // mapper knows how to access it.
                        .flatten(GENERIC_RECORD_SCHEMA, Customer::getRecord, Customer::setRecord)
                        .build();
```

이전 정적 스키마 예제는 다음의 데이터 클래스를 사용합니다.

#### 데이터 클래스
<a name="ddb-en-client-adv-features-flatmap-comp-static-supporting"></a>

------
#### [ Standard data class ]

```
public class Customer {
    private String name;
    private GenericRecord record;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public GenericRecord getRecord() { return this.record; }
    public void setRecord(GenericRecord record) { this.record = record; }

public class GenericRecord {
    private String id;
    private String createdDate;

    public String getId() { return this.id; }
    public void setId(String id) { this.id = id; }

    public String getCreatedDate() { return this.createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

```
@Data
public class Customer {
    private String name;
    private GenericRecord record;
}

@Data
public class GenericRecord {
    private String id;
    private String createdDate;
}
```

------

빌더 패턴을 사용하여 필요한 만큼 다양한 적격 클래스를 평면화할 수 있습니다.

## 다른 코드에 미치는 영향
<a name="ddb-en-client-adv-features-flatmap-compare"></a>

`@DynamoDbFlatten` 속성(또는 `flatten()` 빌더 메서드)을 사용하는 경우 DynamoDB의 항목은 구성된 객체의 각 속성에 대한 속성을 포함합니다. 또한 작성 객체의 속성도 포함됩니다.

반대로 작성된 클래스로 데이터 클래스에 주석을 달고 `@DynamoDbFlatten`를 사용하지 않는 경우 항목은 구성된 객체와 함께 단일 속성으로 저장됩니다.

예를 들어 [평면화 구성 예제](#ddb-en-client-adv-features-flatmap-comp-anno)에 표시된 `Customer` 클래스를 `record` 속성을 평면화한 경우 및 사용하지 않은 경우와 비교해 보세요. 다음 표에 표시된 것처럼 JSON을 사용하여 차이점을 시각화할 수 있습니다.


****  

| 평면화 사용 | 평면화 사용하지 않음 | 
| --- | --- | 
| 속성 3개 | 속성 2개 | 
|  <pre>{<br />  "id": "1",<br />  "createdDate": "today",<br />  "name": "my name"<br />}</pre>  |  <pre>{<br />  "id": "1",<br />  "record": {<br />      "createdDate": "today",<br />      "name": "my name"<br />  }<br />}</pre>  | 

특정 속성을 찾을 것으로 예상하는 다른 코드가 DynamoDB 테이블에 액세스하는 경우 차이가 중요해집니다.

# bean, 맵, 목록 및 세트로 구성된 속성으로 작업
<a name="ddb-en-client-adv-features-nested"></a>

아래 표시된 `Person` 클래스와 같은 bean 정의는 추가 속성이 있는 유형을 참조하는 속성(또는 속성)을 정의할 수 있습니다. 예를 들어 `Person` 클래스에서 `mainAddress`는 추가 값 속성을 정의하는 `Address` bean을 참조하는 속성입니다. `addresses`는 Java Map을 참조하며, 여기서 요소는 `Address` bean을 참조합니다. 이러한 복잡한 유형으로 DynamoDB의 컨텍스트에서 데이터 값에 사용하는 간단한 속성의 컨테이너를 떠올릴 수 있습니다.

DynamoDB는 맵, 목록 또는 bean과 같은 중첩 요소의 값 속성을 *중첩 속성*이라고 합니다. [Amazon DynamoDB 개발자 안내서](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)는 Java 맵, 목록 또는 bean의 저장된 형식을 *문서 유형*으로 참조합니다. Java에서 데이터 값에 사용하는 간단한 속성을 DynamoDB에서 *스칼라 유형*이라고 합니다. 동일한 유형의 여러 스칼라 요소를 포함한 집합을 *집합 유형*이라고 합니다.

DynamoDB 향상된 클라이언트 API는 저장 시 bean 속성을 DynamoDB 맵 문서 유형으로 변환한다는 것을 이해하는 것이 중요합니다.

## `Person` 클래스
<a name="ddb-en-client-adv-features-nested-person"></a>

```
@DynamoDbBean
public class Person {
    private Integer id;
    private String firstName;
    private String lastName;
    private Integer age;
    private Address mainAddress;
    private Map<String, Address> addresses;
    private List<PhoneNumber> phoneNumbers;
    private Set<String> hobbies;

    @DynamoDbPartitionKey
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Address getMainAddress() {
        return mainAddress;
    }

    public void setMainAddress(Address mainAddress) {
        this.mainAddress = mainAddress;
    }

    public Map<String, Address> getAddresses() {
        return addresses;
    }

    public void setAddresses(Map<String, Address> addresses) {
        this.addresses = addresses;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

    public Set<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(Set<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "Person{" +
               "addresses=" + addresses +
               ", id=" + id +
               ", firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               ", age=" + age +
               ", mainAddress=" + mainAddress +
               ", phoneNumbers=" + phoneNumbers +
               ", hobbies=" + hobbies +
               '}';
    }
}
```

## `Address` 클래스
<a name="ddb-en-client-adv-features-nested-address"></a>

```
@DynamoDbBean
public class Address {
    private String street;
    private String city;
    private String state;
    private String zipCode;

    public Address() {
    }

    public String getStreet() {
        return this.street;
    }

    public String getCity() {
        return this.city;
    }

    public String getState() {
        return this.state;
    }

    public String getZipCode() {
        return this.zipCode;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setState(String state) {
        this.state = state;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(street, address.street) && Objects.equals(city, address.city) && Objects.equals(state, address.state) && Objects.equals(zipCode, address.zipCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(street, city, state, zipCode);
    }

    @Override
    public String toString() {
        return "Address{" +
                "street='" + street + '\'' +
                ", city='" + city + '\'' +
                ", state='" + state + '\'' +
                ", zipCode='" + zipCode + '\'' +
                '}';
    }
}
```

## `PhoneNumber` 클래스
<a name="ddb-en-client-adv-features-nested-phonenumber"></a>

```
@DynamoDbBean
public class PhoneNumber {
    String type;
    String number;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "PhoneNumber{" +
                "type='" + type + '\'' +
                ", number='" + number + '\'' +
                '}';
    }
}
```

## 복잡한 유형 저장
<a name="ddb-en-client-adv-features-nested-mapping"></a>

### 주석이 달린 데이터 클래스 사용
<a name="ddb-en-client-adv-features-nested-map-anno"></a>

사용자 정의 클래스에 주석을 달기만 하면 사용자 정의 클래스의 중첩 속성을 저장할 수 있습니다. 이전에 표시된 `Address` 클래스와 `PhoneNumber` 클래스에는 `@DynamoDbBean` 주석만 있는 주석이 달려 있습니다. DynamoDB 향상된 클라이언트 API가 다음 코드 조각을 사용하여 클래스에 대한 `Person` 테이블 스키마를 구축하면 API는 `Address` 및 `PhoneNumber` 클래스의 사용을 발견하고 DynamoDB와 함께 작동하도록 해당 매핑을 구축합니다.

```
TableSchema<Person> personTableSchema = TableSchema.fromBean(Person.class);
```

### 빌더에서 추상 스키마 사용
<a name="ddb-en-client-adv-features-nested-map-builder"></a>

다른 접근 방식은 다음 코드에 표시된 대로 각 중첩 bean 클래스에 대해 정적 테이블 스키마 빌더를 사용하는 것입니다.

`Address` 및 `PhoneNumber` 클래스의 테이블 스키마는 DynamoDB 테이블과 함께 사용할 수 없다는 점에서 추상적입니다. 기본 키에 대한 정의가 없기 때문입니다. 하지만 `Person` 클래스의 테이블 스키마에서는 중첩 스키마로 사용됩니다.

`PERSON_TABLE_SCHEMA`의 정의에서 주석 줄 1과 2줄 뒤에 추상 테이블 스키마를 사용하는 코드가 표시됩니다. `EnhanceType.documentOf(...)`메서드의 `documentOf`를 사용한다고 해서 해당 메서드가 확장 문서 API `EnhancedDocument` 유형을 반환한다는 의미는 아닙니다. 이 컨텍스트의 `documentOf(...)` 메서드는 테이블 스키마 인수를 사용하여 클래스 인수를 DynamoDB 테이블 속성에 매핑하거나 DynamoDB 테이블 속성에서 클래스 인수를 매핑하는 방법을 알고 있는 객체를 반환합니다.

#### 정적 스키마 코드
<a name="ddb-en-client-adv-features-nested-map-builder-code"></a>

```
    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<Address> TABLE_SCHEMA_ADDRESS = TableSchema.builder(Address.class)
        .newItemSupplier(Address::new)
        .addAttribute(String.class, a -> a.name("street")
            .getter(Address::getStreet)
            .setter(Address::setStreet))
        .addAttribute(String.class, a -> a.name("city")
            .getter(Address::getCity)
            .setter(Address::setCity))
        .addAttribute(String.class, a -> a.name("zipcode")
            .getter(Address::getZipCode)
            .setter(Address::setZipCode))
        .addAttribute(String.class, a -> a.name("state")
            .getter(Address::getState)
            .setter(Address::setState))
        .build();

    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<PhoneNumber> TABLE_SCHEMA_PHONENUMBER = TableSchema.builder(PhoneNumber.class)
        .newItemSupplier(PhoneNumber::new)
        .addAttribute(String.class, a -> a.name("type")
            .getter(PhoneNumber::getType)
            .setter(PhoneNumber::setType))
        .addAttribute(String.class, a -> a.name("number")
            .getter(PhoneNumber::getNumber)
            .setter(PhoneNumber::setNumber))
        .build();

    // A static table schema that can be used with a DynamoDB table.
    // The table schema contains two nested schemas that are used to perform mapping to/from DynamoDB.
    public static final TableSchema<Person> PERSON_TABLE_SCHEMA =
        TableSchema.builder(Person.class)
            .newItemSupplier(Person::new)
            .addAttribute(Integer.class, a -> a.name("id")
                .getter(Person::getId)
                .setter(Person::setId)
                .addTag(StaticAttributeTags.primaryPartitionKey()))
            .addAttribute(String.class, a -> a.name("firstName")
                .getter(Person::getFirstName)
                .setter(Person::setFirstName))
            .addAttribute(String.class, a -> a.name("lastName")
                .getter(Person::getLastName)
                .setter(Person::setLastName))
            .addAttribute(Integer.class, a -> a.name("age")
                .getter(Person::getAge)
                .setter(Person::setAge))
            .addAttribute(EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS), a -> a.name("mainAddress")
                .getter(Person::getMainAddress)
                .setter(Person::setMainAddress))
            .addAttribute(EnhancedType.listOf(String.class), a -> a.name("hobbies")
                .getter(Person::getHobbies)
                .setter(Person::setHobbies))
            .addAttribute(EnhancedType.mapOf(
                EnhancedType.of(String.class),
                // 1. Use mapping functionality of the Address table schema.
                EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS)), a -> a.name("addresses")
                .getter(Person::getAddresses)
                .setter(Person::setAddresses))
            .addAttribute(EnhancedType.listOf(
                // 2. Use mapping functionality of the PhoneNumber table schema.
                EnhancedType.documentOf(PhoneNumber.class, TABLE_SCHEMA_PHONENUMBER)), a -> a.name("phoneNumbers")
                .getter(Person::getPhoneNumbers)
                .setter(Person::setPhoneNumbers))
            .build();
```

## 복합 유형의 프로젝트 속성
<a name="ddb-en-client-adv-features-nested-projection"></a>

`query()` 및 `scan()` 메서드의 경우, `addNestedAttributeToProject()` 및 `attributesToProject()`와 같은 메서드 호출을 사용하여 결과에 반환하려는 속성을 지정할 수 있습니다. DynamoDB 향상된 클라이언트 API는 요청을 보내기 전에 Java 메서드 호출 파라미터를 [프로젝션 표현식](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)으로 변환합니다.

다음 예제는 `Person` 테이블을 두 항목으로 채운 다음 세 번의 스캔 작업을 수행합니다.

첫 번째 스캔에서는 결과를 다른 스캔 작업과 비교하기 위해 테이블의 모든 항목에 액세스합니다.

두 번째 스캔에서는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)) 빌더 메서드를 사용하여 `street` 속성 값만 반환합니다.

세 번째 스캔 작업에서는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)) 빌더 메서드를 사용하여 첫 번째 수준 속성 `hobbies`에 대한 데이터를 반환합니다. `hobbies`의 속성 유형은 리스트입니다. 개별 목록 항목에 접근하려면 목록에서 `get()` 작업을 수행하세요.

```
        personDynamoDbTable = getDynamoDbEnhancedClient().table("Person", PERSON_TABLE_SCHEMA);
        PersonUtils.createPersonTable(personDynamoDbTable, getDynamoDbClient());
        // Use a utility class to add items to the Person table.
        List<Person> personList = PersonUtils.getItemsForCount(2);
        // This utility method performs a put against DynamoDB to save the instances in the list argument.
        PersonUtils.putCollection(getDynamoDbEnhancedClient(), personList, personDynamoDbTable);

        // The first scan logs all items in the table to compare to the results of the subsequent scans.
        final PageIterable<Person> allItems = personDynamoDbTable.scan();
        allItems.items().forEach(p ->
                // 1. Log what is in the table.
                logger.info(p.toString()));

        // Scan for nested attributes.
        PageIterable<Person> streetScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'addNestedAttributeToProject()' or 'addNestedAttributesToProject()' to access data nested in maps in DynamoDB.
                .addNestedAttributeToProject(
                        NestedAttributeName.create("addresses", "work", "street")
                ));

        streetScanResult.items().forEach(p ->
                //2. Log the results of requesting nested attributes.
                logger.info(p.toString()));

        // Scan for a top-level list attribute.
        PageIterable<Person> hobbiesScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'attributesToProject()' method to access first-level attributes.
                .attributesToProject("hobbies"));

        hobbiesScanResult.items().forEach((p) -> {
            // 3. Log the results of the request for the 'hobbies' attribute.
            logger.info(p.toString());
            // To access an item in a list, first get the parent attribute, 'hobbies', then access items in the list.
            String hobby = p.getHobbies().get(1);
            // 4. Log an item in the list.
            logger.info(hobby);
        });
```

```
// Logged results from comment line 1.
Person{id=2, firstName='first name 2', lastName='last name 2', age=11, addresses={work=Address{street='street 21', city='city 21', state='state 21', zipCode='33333'}, home=Address{street='street 2', city='city 2', state='state 2', zipCode='22222'}}, phoneNumbers=[PhoneNumber{type='home', number='222-222-2222'}, PhoneNumber{type='work', number='333-333-3333'}], hobbies=[hobby 2, hobby 21]}
Person{id=1, firstName='first name 1', lastName='last name 1', age=11, addresses={work=Address{street='street 11', city='city 11', state='state 11', zipCode='22222'}, home=Address{street='street 1', city='city 1', state='state 1', zipCode='11111'}}, phoneNumbers=[PhoneNumber{type='home', number='111-111-1111'}, PhoneNumber{type='work', number='222-222-2222'}], hobbies=[hobby 1, hobby 11]}

// Logged results from comment line 2.
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}

// Logged results from comment lines 3 and 4.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
hobby 21
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
hobby 11
```

**참고**  
`attributesToProject()` 메서드가 프로젝션하려는 속성을 추가하는 다른 빌더 메서드를 따르는 경우 `attributesToProject()`에 제공된 속성 이름 목록이 다른 모든 속성 이름을 대체합니다.  
다음 코드 조각의 `ScanEnhancedRequest` 인스턴스를 사용하여 스캔을 수행하면 취미 데이터만 반환됩니다.  

```
ScanEnhancedRequest lastOverwrites = ScanEnhancedRequest.builder()
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("firstName")
        // If the 'attributesToProject()' method follows other builder methods that add attributes for projection,
        // its list of attributes replace all previous attributes.
        .attributesToProject("hobbies")
        .build();
PageIterable<Person> hobbiesOnlyResult = personDynamoDbTable.scan(lastOverwrites);
hobbiesOnlyResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```
다음 코드 조각은 `attributesToProject()` 메서드를 먼저 사용합니다. 이 순서는 요청된 다른 모든 속성을 보존합니다.  

```
ScanEnhancedRequest attributesPreserved = ScanEnhancedRequest.builder()
        // Use 'attributesToProject()' first so that the method call does not replace all other attributes
        // that you want to project.
        .attributesToProject("firstName")
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("hobbies")
        .build();
PageIterable<Person> allAttributesResult = personDynamoDbTable.scan(attributesPreserved);
allAttributesResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='first name 2', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='first name 1', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```

## 표현식에서 복잡한 유형 사용
<a name="ddb-en-client-adv-features-nested-expressions"></a>

참조 해제 연산자를 사용하여 복합 유형의 구조를 탐색해 필터 표현식 및 조건 표현식과 같은 복합 유형을 표현식에 사용할 수 있습니다. 객체 및 맵의 경우 `. (dot)` 및 `[n]`을 사용하는 목록 요소를 사용합니다(요소의 시퀀스 번호 주변으로 대괄호). 집합의 개별 요소를 참조할 수는 없지만 [`contains` 함수](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)를 사용할 수 있습니다.

다음 예제에서는 스캔 작업에 사용되는 2개의 필터 표현식을 보여줍니다. 필터 표현식은 결과에서 원하는 항목의 일치 조건을 지정합니다. 다음 예제에서는 이전에 표시된 `Person`, `Address`, `PhoneNumber` 클래스를 사용합니다.

```
    public void scanUsingFilterOfNestedAttr() {
        // The following is a filter expression for an attribute that is a map of Address objects.
        // By using this filter expression, the SDK returns Person objects that have an address
        // with 'mailing' as a key and 'MS2' for a state value.
        Expression addressFilter = Expression.builder()
                .expression("addresses.#type.#field = :value")
                .putExpressionName("#type", "mailing")
                .putExpressionName("#field", "state")
                .putExpressionValue(":value", AttributeValue.builder().s("MS2").build())
                .build();

        PageIterable<Person> addressFilterResults = personDynamoDbTable.scan(rb -> rb.
                filterExpression(addressFilter));
        addressFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert addressFilterResults.items().stream().count() == 1;


        // The following is a filter expression for an attribute that is a list of phone numbers.
        // By using this filter expression, the SDK returns Person objects whose second phone number
        // in the list has a type equal to 'cell'.
        Expression phoneFilter = Expression.builder()
                .expression("phoneNumbers[1].#type = :type")
                .putExpressionName("#type", "type")
                .putExpressionValue(":type", AttributeValue.builder().s("cell").build())
                .build();

        PageIterable<Person> phoneFilterResults = personDynamoDbTable.scan(rb -> rb
                .filterExpression(phoneFilter)
                .attributesToProject("id", "firstName", "lastName", "phoneNumbers")
        );

        phoneFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert phoneFilterResults.items().stream().count() == 1;
        assert phoneFilterResults.items().stream().findFirst().get().getPhoneNumbers().get(1).getType().equals("cell");
    }
```

### 테이블을 채우는 헬퍼 메서드
<a name="nested-expressions-helper-method"></a>

```
    public static void populateDatabase() {
        Person person1 = new Person();
        person1.setId(1);
        person1.setFirstName("FirstName1");
        person1.setLastName("LastName1");

        Address billingAddr1 = new Address();
        billingAddr1.setState("BS1");
        billingAddr1.setCity("BillingTown1");

        Address mailing1 = new Address();
        mailing1.setState("MS1");
        mailing1.setCity("MailingTown1");

        person1.setAddresses(Map.of("billing", billingAddr1, "mailing", mailing1));

        PhoneNumber pn1_1 = new PhoneNumber();
        pn1_1.setType("work");
        pn1_1.setNumber("111-111-1111");

        PhoneNumber pn1_2 = new PhoneNumber();
        pn1_2.setType("home");
        pn1_2.setNumber("222-222-2222");

        List<PhoneNumber> phoneNumbers1 = List.of(pn1_1, pn1_2);
        person1.setPhoneNumbers(phoneNumbers1);

        personDynamoDbTable.putItem(person1);

        Person person2 = person1;
        person2.setId(2);
        person2.setFirstName("FirstName2");
        person2.setLastName("LastName2");

        Address billingAddress2 = billingAddr1;
        billingAddress2.setCity("BillingTown2");
        billingAddress2.setState("BS2");

        Address mailing2 = mailing1;
        mailing2.setCity("MailingTown2");
        mailing2.setState("MS2");

        person2.setAddresses(Map.of("billing", billingAddress2, "mailing", mailing2));

        PhoneNumber pn2_1 = new PhoneNumber();
        pn2_1.setType("work");
        pn2_1.setNumber("333-333-3333");

        PhoneNumber pn2_2 = new PhoneNumber();
        pn2_2.setType("cell");
        pn2_2.setNumber("444-444-4444");

        List<PhoneNumber> phoneNumbers2 = List.of(pn2_1, pn2_2);
        person2.setPhoneNumbers(phoneNumbers2);

        personDynamoDbTable.putItem(person2);
    }
```

### 데이터베이스 내 항목의 JSON 표현
<a name="nested-attributes-expression-json-items"></a>

```
{
 "id": 1,
 "addresses": {
  "billing": {
   "city": "BillingTown1",
   "state": "BS1",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown1",
   "state": "MS1",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName1",
 "lastName": "LastName1",
 "phoneNumbers": [
  {
   "number": "111-111-1111",
   "type": "work"
  },
  {
   "number": "222-222-2222",
   "type": "home"
  }
 ]
}

{
 "id": 2,
 "addresses": {
  "billing": {
   "city": "BillingTown2",
   "state": "BS2",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown2",
   "state": "MS2",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName2",
 "lastName": "LastName2",
 "phoneNumbers": [
  {
   "number": "333-333-3333",
   "type": "work"
  },
  {
   "number": "444-444-4444",
   "type": "cell"
  }
 ]
}
```

## 복잡한 유형이 포함된 항목 업데이트
<a name="ddb-en-client-adv-features-nested-updates"></a>

복잡한 유형이 포함된 항목을 업데이트할 수 있는 2가지 기본 접근 방식이 있습니다.
+ 접근 방식 1: 먼저 항목을 검색하고(`getItem` 사용) 객체를 업데이트한 다음 `DynamoDbTable#updateItem`을 직접적으로 호출합니다.
+ 접근 방식 2: 항목을 검색하지 말고 새 인스턴스를 구문화하고, 업데이트할 속성을 설정하고, 적절한 값의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html)를 설정하여 인스턴스를 `DynamoDbTable#updateItem`에 제출합니다. 이 접근 방식에서는 항목을 업데이트하기 전에 항목을 가져오지 않아도 됩니다.

이 섹션에 표시된 예제에서는 이전에 표시된 `Person`, `Address`, `PhoneNumber` 클래스를 사용합니다.

### 업데이트 접근 방식 1: 검색 후 업데이트
<a name="ddb-en-client-adv-features-nested-updates-retreive"></a>

이 접근 방식을 사용하면 업데이트 시 데이터가 손실되지 않도록 할 수 있습니다. DynamoDB 향상된 클라이언트 API는 복합 유형의 값을 포함하여 DynamoDB에 저장된 항목의 속성을 사용하여 bean을 다시 만듭니다. 그런 다음 getter와 setter 메서드를 사용하여 bean을 업데이트해야 합니다. 이 접근 방식의 단점은 먼저 항목을 검색하는 데 비용이 든다는 것입니다.

다음 예제는 업데이트하기 전에 항목을 처음 검색하는 경우 데이터가 손실되지 않음을 보여줍니다.

```
    public void retrieveThenUpdateExample()  {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");
        person.setLastName("LastName");

        Address mainAddress = new Address();
        mainAddress.setStreet("123 MyStreet");
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        mainAddress.setZipCode("MyZipCode");
        person.setMainAddress(mainAddress);

        PhoneNumber homePhone = new PhoneNumber();
        homePhone.setNumber("1111111");
        homePhone.setType("HOME");
        person.setPhoneNumbers(List.of(homePhone));

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.
        // First, retrieve the item
        Person retrievedPerson = personDynamoDbTable.getItem(Key.builder().partitionValue(1).build());

        // Make any updates.
        retrievedPerson.getMainAddress().setCity("YourCity");

        // Save the updated bean. 'updateItem' returns the bean as it appears after the update.
        Person updatedPerson = personDynamoDbTable.updateItem(retrievedPerson);

        // Verify for this example.
        Address updatedMainAddress = updatedPerson.getMainAddress();
        assert updatedMainAddress.getCity().equals("YourCity");
        assert updatedMainAddress.getState().equals("MyState"); // Unchanged.
        // The list of phone numbers remains; it was not set to null;
        assert updatedPerson.getPhoneNumbers().size() == 1;
    }
```

### 업데이트 접근 방식 2: 먼저 항목을 검색하지 않고 `IgnoreNullsMode` 열거형 사용
<a name="ddb-en-client-adv-features-nested-updates-nullmode"></a>

DynamoDB에서 항목을 업데이트하려면 업데이트하려는 속성만 있는 새 객체를 제공하고 다른 값은 null로 둘 수 있습니다. 이 접근 방식을 사용하려면 SDK에서 객체의 null 값을 처리하는 방법과 동작을 제어하는 방법을 알고 있어야 합니다.

SDK에서 무시할 null 값 속성을 지정하려면 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html)를 구축할 때 `IgnoreNullsMode` 열거형을 제공합니다. 열거 값 중 하나를 사용하는 예제로 다음 코드 조각은 `IgnoreNullsMode.SCALAR_ONLY` 모드를 사용합니다.

```
// Create a new Person object to update the existing item in DynamoDB.
Person personForUpdate = new Person();
personForUpdate.setId(1);
personForUpdate.setFirstName("updatedFirstName");  // 'firstName' is a top scalar property.

Address addressForUpdate = new Address();
addressForUpdate.setCity("updatedCity");
personForUpdate.setMainAddress(addressForUpdate);

personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY));

/* With IgnoreNullsMode.SCALAR_ONLY provided, The SDK ignores all null properties. The SDK adds or replaces
the 'firstName' property with the provided value, "updatedFirstName". The SDK updates the 'city' value of
'mainAddress', as long as the 'mainAddress' attribute already exists in DynamoDB.

In the background, the SDK generates an update expression that it sends in the request to DynamoDB.
The following JSON object is a simplified version of what it sends. Notice that the SDK includes the paths
to 'mainAddress.city' and 'firstName' in the SET clause of the update expression. No null values in
'personForUpdate' are included.

{
  "TableName": "PersonTable",
  "Key": {
    "id": {
      "N": "1"
    }
  },
  "ReturnValues": "ALL_NEW",
  "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city, #firstName = :firstName",
  "ExpressionAttributeNames": {
    "#city": "city",
    "#firstName": "firstName",
    "#mainAddress": "mainAddress"
  },
  "ExpressionAttributeValues": {
    ":firstName": {
      "S": "updatedFirstName"
    },
    ":mainAddress_city": {
      "S": "updatedCity"
    }
  }
}

Had we chosen 'IgnoreNullsMode.DEFAULT' instead of 'IgnoreNullsMode.SCALAR_ONLY', the SDK would have included
null values in the "ExpressionAttributeValues" section of the request as shown in the following snippet.

  "ExpressionAttributeValues": {
    ":mainAddress": {
      "M": {
        "zipCode": {
          "NULL": true
        },
        "city": {
          "S": "updatedCity"
        },
        "street": {
          "NULL": true
        },
        "state": {
          "NULL": true
        }
      }
    },
    ":firstName": {
      "S": "updatedFirstName"
    }
  }
*/
```

[업데이트 표현식](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)에 대한 자세한 내용은 Amazon DynamoDB 개발자 안내서를 참조하세요.

#### `IgnoreNullsMode` 옵션에 대한 설명
<a name="ignore-nulls-mode-descriptions"></a>
+ `IgnoreNullsMode.SCALAR_ONLY` - 이 설정을 사용하여 모든 수준에서 스칼라 속성을 업데이트합니다. SDK는 null이 아닌 스칼라 속성만 DynamoDB로 보내는 업데이트 문을 구문화합니다. SDK는 bean 또는 맵의 null 값 스칼라 속성을 무시하여 DynamoDB에 저장된 값을 유지합니다.

  맵 또는 bean의 스칼라 속성을 업데이트할 때 맵이 DynamoDB에 이미 있어야 합니다. DynamoDB의 객체에서 아직 존재하지 않는 객체에 대한 맵 또는 bean을 추가하면 *The document path provided in the update expression is invalid for update*라는 메시지가 `DynamoDbException`에 표시됩니다. 속성을 업데이트하기 전에 `MAPS_ONLY` 모드를 사용하여 DynamoDB에 bean 또는 맵을 추가해야 합니다.
+ `IgnoreNullsMode.MAPS_ONLY` - 이 설정을 사용하여 bean 또는 맵인 속성을 추가하거나 대체합니다. SDK는 객체에 제공된 맵 또는 bean을 대체하거나 추가합니다. 객체에 null인 모든 bean 또는 맵은 무시되어 DynamoDB에 있는 맵이 유지됩니다.
+ `IgnoreNullsMode.DEFAULT` - 이 설정을 사용하면 SDK가 null 값을 무시하지 않습니다. null인 모든 수준의 스칼라 속성은 null로 업데이트됩니다. SDK는 객체의 null 값 bean, 맵, 목록 또는 설정 속성을 DynamoDB의 null로 업데이트합니다. 이 모드를 사용하거나 기본 모드이므로 모드를 제공하지 않는 경우, 값을 null로 설정하려는 것이 아니라면 DynamoDB의 값이 업데이트를 위해 객체에 제공된 null로 설정되지 않도록 먼저 항목을 검색해야 합니다.

모든 모드에서 null이 아닌 목록 또는 세트가 있는 `updateItem`에 객체를 제공하면 목록 또는 세트가 DynamoDB에 저장됩니다.

#### 모드가 필요한 이유
<a name="ddb-en-client-adv-features-nested-updates-nullmodes-why"></a>

객체에 bean을 제공하거나 `updateItem` 메서드에 매핑하면 SDK는 bean의 속성 값(또는 맵의 항목 값)을 사용하여 항목을 업데이트해야 하는지 또는 전체 bean/맵이 DynamoDB에 저장된 항목을 대체해야 하는지 알 수 없습니다.

항목 검색을 먼저 보여주는 이전 예제에서 검색 없이 `mainAddress`의 `city` 속성을 업데이트해 보겠습니다.

```
/* The retrieval example saved the Person object with a 'mainAddress' property whose 'city' property value is "MyCity".
/* Note that we create a new Person with only the necessary information to update the city value
of the mainAddress. */
Person personForUpdate = new Person();
personForUpdate.setId(1);
// The update we want to make changes the city.
Address mainAddressForUpdate = new Address();
mainAddressForUpdate.setCity("YourCity");
personForUpdate.setMainAddress(mainAddressForUpdate);

// Lets' try the following:
Person updatedPerson = personDynamoDbTable.updateItem(personForUpdate);
/*
 Since we haven't retrieved the item, we don't know if the 'mainAddress' property
 already exists, so what update expression should the SDK generate?

A) Should it replace or add the 'mainAddress' with the provided object (setting all attributes to null other than city)
   as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress = :mainAddress",
        "ExpressionAttributeNames": {
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress": {
            "M": {
              "zipCode": {
                "NULL": true
              },
              "city": {
                "S": "YourCity"
              },
              "street": {
                "NULL": true
              },
              "state": {
                "NULL": true
              }
            }
          }
        }
      }
 
B) Or should it update only the 'city' attribute of an existing 'mainAddress' as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city",
        "ExpressionAttributeNames": {
          "#city": "city",
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress_city": {
            "S": "YourCity"
          }
        }
      }

However, assume that we don't know if the 'mainAddress' already exists. If it doesn't exist, the SDK would try to update 
an attribute of a non-existent map, which results in an exception.

In this particular case, we would likely select option B (SCALAR_ONLY) to retain the other values of the 'mainAddress'.
*/
```

다음 두 예제에서는 `MAPS_ONLY` 및 `SCALAR_ONLY` 열거형 값의 사용을 보여줍니다. `MAPS_ONLY`는 맵을 추가하고 `SCALAR_ONLY`는 맵을 업데이트합니다.

##### `IgnoreNullsMode.MAPS_ONLY` 예
<a name="scalar-only-example"></a>

```
    public void mapsOnlyModeExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);


        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); // Since the mainAddress property does not exist, use MAPS_ONLY mode.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState() == null;
    }
```

##### `IgnoreNullsMode.SCALAR_ONLY example`
<a name="maps-only-example"></a>

```
    public void scalarOnlyExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        Address mainAddress = new Address();
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        person.setMainAddress(mainAddress);

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);

        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); // SCALAR_ONLY mode ignores null properties in the in mainAddress.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState().equals("MyState"); // The state property remains the same.
    }
```

각 모드에 대해 무시되는 null 값을 확인하려면 다음 표를 참조하세요. bean 또는 맵으로 작업하는 경우를 제외하고 `SCALAR_ONLY` 및 `MAPS_ONLY`로 작업할 수 있습니다.


**SDK가 각 모드를 무시하는 `updateItem`에 제출된 객체의 null 값 속성은 무엇인가요?**  

| 속성 유형 | SCALAR\$1ONLY 모드 | MAPS\$1ONLY 모드 | DEFAULT 모드 | 
| --- | --- | --- | --- | 
| 상위 스칼라 | 예 | 예 | 아니요 | 
| bean 또는 맵 | 예 | 예 | 아니요 | 
| bean 또는 맵 항목의 스칼라 값 | 예1 | 아니요2 | 아니요 | 
| 목록 또는 세트 | 예 | 예 | 아니요 | 

1이는 맵이 DynamoDB에 이미 있다고 가정합니다. 업데이트를 위해 객체에 제공하는 bean 또는 맵의 null이거나 null이 아닌 모든 스칼라 값은 값의 경로가 DynamoDB에 있어야 합니다. SDK는 요청을 제출하기 전에 `. (dot)` 역참조 연산자를 사용하여 속성에 대한 경로를 구문화합니다.

2`MAPS_ONLY` 모드를 사용하여 bean 또는 맵을 완전히 대체하거나 추가하면 bean 또는 맵의 모든 null 값이 DynamoDB에 저장된 맵에 유지됩니다.

# `@DynamoDbPreserveEmptyObject`을 사용하여 객체를 보존
<a name="ddb-en-client-adv-features-empty"></a>

빈 객체가 있는 Bean을 Amazon DynamoDB에 저장하고 검색 시 SDK가 빈 객체를 다시 생성하도록 하려면 내부 Bean의 접근자에 `@DynamoDbPreserveEmptyObject`로 주석을 답니다.

주석이 작동하는 방식을 설명하기 위해 코드 예제에서는 다음 두 개의 Bean을 사용합니다.

## 예시 beans
<a name="ddb-en-client-adv-features-empty-ex1"></a>

다음 데이터 클래스에는 두 개의 `InnerBean` 필드가 있습니다. 접근자 메서드 `getInnerBeanWithoutAnno()`에는 `@DynamoDbPreserveEmptyObject`로 주석이 달리지 않습니다. `getInnerBeanWithAnno()` 메서드에는 주석이 달려 있습니다.

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithAnno;

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

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbPreserveEmptyObject
    public InnerBean getInnerBeanWithAnno() { return innerBeanWithAnno; }
    public void setInnerBeanWithAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithAnno=" + innerBeanWithAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

다음 `InnerBean` 클래스의 인스턴스는 `MyBean`의 필드이며 다음 예제 코드에서는 빈 객체로 초기화됩니다.

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanField;

    public String getInnerBeanField() {
        return innerBeanField;
    }

    public void setInnerBeanField(String innerBeanField) {
        this.innerBeanField = innerBeanField;
    }

    @Override
    public String toString() {
        return "InnerBean{" +
                "innerBeanField='" + innerBeanField + '\'' +
                '}';
    }
}
```

다음 코드 예제는 초기화된 내부 bean이 있는 `MyBean` 객체를 DynamoDB에 저장한 후에 항목을 가져옵니다. 기록된 출력은 `innerBeanWithoutAnno`는 초기화되지 않았지만 `innerBeanWithAnno`가 생성되었음을 보여줍니다.

```
    public MyBean preserveEmptyObjectAnnoUsingGetItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Save an item to DynamoDB.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(new InnerBean());   // Instantiate the inner bean.
        bean.setInnerBeanWithAnno(new InnerBean());      // Instantiate the inner bean.
        myBeanTable.putItem(bean);

        GetItemEnhancedRequest request = GetItemEnhancedRequest.builder()
                .key(Key.builder().partitionValue("1").build())
                .build();
        MyBean myBean = myBeanTable.getItem(request);

        logger.info(myBean.toString());
        // Output 'MyBean[innerBeanWithoutAnno=null, innerBeanWithAnno=InnerBean{innerBeanField='null'}, id='1', name='null']'.

        return myBean;
    }
```

## 대체 정적 스키마
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

Bean의 주석 대신 다음 `StaticTableSchema` 버전의 테이블 스키마를 사용할 수 있습니다.

```
    public static TableSchema<MyBean> buildStaticSchemas() {

        StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
                StaticTableSchema.builder(InnerBean.class)
                        .newItemSupplier(InnerBean::new)
                        .addAttribute(String.class, a -> a.name("innerBeanField")
                                .getter(InnerBean::getInnerBeanField)
                                .setter(InnerBean::setInnerBeanField))
                        .build();

        return StaticTableSchema.builder(MyBean.class)
                .newItemSupplier(MyBean::new)
                .addAttribute(String.class, a -> a.name("id")
                        .getter(MyBean::getId)
                        .setter(MyBean::setId)
                        .addTag(primaryPartitionKey()))
                .addAttribute(String.class, a -> a.name("name")
                        .getter(MyBean::getName)
                        .setter(MyBean::setName))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema),
                        a -> a.name("innerBean1")
                                .getter(MyBean::getInnerBeanWithoutAnno)
                                .setter(MyBean::setInnerBeanWithoutAnno))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema,
                                b -> b.preserveEmptyObject(true)),
                        a -> a.name("innerBean2")
                                .getter(MyBean::getInnerBeanWithAnno)
                                .setter(MyBean::setInnerBeanWithAnno))
                .build();
    }
```

# 중첩된 개체의 null 속성을 저장하지 마세요
<a name="ddb-en-client-adv-features-ignore-null"></a>

`@DynamoDbIgnoreNulls` 주석을 적용하여 DynamoDB에 데이터 클래스 객체를 저장할 때 중첩된 객체의 null 속성을 건너뛸 수 있습니다. 반대로 null 값이 있는 최상위 속성은 데이터베이스에 저장되지 않습니다.

주석이 작동하는 방식을 설명하기 위해 코드 예제에서는 다음 두 개의 Bean을 사용합니다.

## 예시 beans
<a name="ddb-en-client-adv-features-ignore-null-ex1"></a>

다음 데이터 클래스에는 두 개의 `InnerBean` 필드가 있습니다. 접근자 메서드 `getInnerBeanWithoutAnno()`에는 주석이 달려 있지 않습니다. `getInnerBeanWithIgnoreNullsAnno()` 메서드에는 `@DynamoDbIgnoreNulls`로 주석이 달려 있습니다.

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithIgnoreNullsAnno;

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

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbIgnoreNulls
    public InnerBean getInnerBeanWithIgnoreNullsAnno() { return innerBeanWithIgnoreNullsAnno; }
    public void setInnerBeanWithIgnoreNullsAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithIgnoreNullsAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithIgnoreNullsAnno=" + innerBeanWithIgnoreNullsAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

다음 `InnerBean` 클래스의 인스턴스는 `MyBean`의 필드이며 다음 예제 코드에서 사용됩니다.

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanFieldString;
    private Integer innerBeanFieldInteger;

    public String getInnerBeanFieldString() { return innerBeanFieldString; }
    public void setInnerBeanFieldString(String innerBeanFieldString) { this.innerBeanFieldString = innerBeanFieldString; }

    public Integer getInnerBeanFieldInteger() { return innerBeanFieldInteger; }
    public void setInnerBeanFieldInteger(Integer innerBeanFieldInteger) { this.innerBeanFieldInteger = innerBeanFieldInteger; }

    @Override
    public String toString() {
        return new StringJoiner(", ", InnerBean.class.getSimpleName() + "[", "]")
                .add("innerBeanFieldString='" + innerBeanFieldString + "'")
                .add("innerBeanFieldInteger=" + innerBeanFieldInteger)
                .toString();
    }
}
```

다음 코드 예제에서는 `InnerBean` 객체를 만들고 객체의 두 속성 중 하나만 값으로 설정합니다.

```
    public void ignoreNullsAnnoUsingPutItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Create an InnerBean object and give only one attribute a value.
        InnerBean innerBeanOneAttributeSet = new InnerBean();
        innerBeanOneAttributeSet.setInnerBeanFieldInteger(200);

        // Create a MyBean instance and use the same InnerBean instance both for attributes.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(innerBeanOneAttributeSet);
        bean.setInnerBeanWithIgnoreNullsAnno(innerBeanOneAttributeSet);

        Map<String, AttributeValue> itemMap = myBeanTable.tableSchema().itemToMap(bean, true);
        logger.info(itemMap.toString());
        // Log the map that is sent to the database.
        // {innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)}), id=AttributeValue(S=1), innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})}
        
        // Save the MyBean object to the table.
        myBeanTable.putItem(bean);
    }
```

DynamoDB로 전송되는 하위 수준 데이터를 시각화하기 위해 코드는 `MyBean` 객체를 저장하기 전에 속성 맵을 기록합니다.

로깅된 출력은 `innerBeanWithIgnoreNullsAnno`가 속성 하나를 출력함을 보여줍니다.

```
innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)})
```

`innerBeanWithoutAnno` 인스턴스는 두 개의 속성을 출력합니다. 한 속성의 값은 200이고 다른 하나는 null 값 속성입니다.

```
innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})
```

## 속성 맵의 JSON 표현
<a name="ddb-en-client-adv-features-ignore-null-ex2"></a>

다음 JSON 표현을 사용하면 DynamoDB에 저장된 데이터를 더 쉽게 확인할 수 있습니다.

```
{
  "id": {
    "S": "1"
  },
  "innerBeanWithIgnoreNullsAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      }
    }
  },
  "innerBeanWithoutAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      },
      "innerBeanFieldString": {
        "NULL": true
      }
    }
  }
}
```

## 대체 정적 스키마
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

다음 `StaticTableSchema` 버전의 테이블 스키마를 데이터 클래스 주석에 사용할 수 있습니다.

```
public static TableSchema<MyBean> buildStaticSchemas() {

    StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
        StaticTableSchema.builder(InnerBean.class)
            .newItemSupplier(InnerBean::new)
            .addAttribute(String.class, a -> a.name("innerBeanFieldString")
                .getter(InnerBean::getInnerBeanFieldString)
                .setter(InnerBean::setInnerBeanFieldString))
            .addAttribute(Integer.class, a -> a.name("innerBeanFieldInteger")
                .getter(InnerBean::getInnerBeanFieldInteger)
                .setter(InnerBean::setInnerBeanFieldInteger))
            .build();

    return StaticTableSchema.builder(MyBean.class)
        .newItemSupplier(MyBean::new)
        .addAttribute(String.class, a -> a.name("id")
            .getter(MyBean::getId)
            .setter(MyBean::setId)
            .addTag(primaryPartitionKey()))
        .addAttribute(String.class, a -> a.name("name")
            .getter(MyBean::getName)
            .setter(MyBean::setName))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema),
            a -> a.name("innerBeanWithoutAnno")
                .getter(MyBean::getInnerBeanWithoutAnno)
                .setter(MyBean::setInnerBeanWithoutAnno))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema,
                b -> b.ignoreNulls(true)),
            a -> a.name("innerBeanWithIgnoreNullsAnno")
                .getter(MyBean::getInnerBeanWithIgnoreNullsAnno)
                .setter(MyBean::setInnerBeanWithIgnoreNullsAnno))
        .build();
}
```

# DynamoDB용 향상된 문서 API를 사용하여 JSON 문서로 작업
<a name="ddb-en-client-doc-api"></a>

용 [향상된 문서 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html) AWS SDK for Java 2.x 는 고정된 스키마가 없는 문서 지향 데이터로 작동하도록 설계되었습니다. 하지만 사용자 지정 클래스를 사용하여 개별 속성을 매핑할 수도 있습니다.

 향상된 문서 API는 AWS SDK for Java v1.x의 [문서 API](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/DynamoDB.html)의 후속 버전입니다.

**Contents**
+ [향상된 문서 API 사용 시작](ddb-en-client-doc-api-steps.md)
  + [`DocumentTableSchema` 및 `DynamoDbTable` 만들기](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema)
+ [향상된 문서 빌드](ddb-en-client-doc-api-steps-create-ed.md)
  + [JSON 문자열로 빌드](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromJson)
  + [개별 요소로 빌드](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)
+ [CRUD 작업을 수행](ddb-en-client-doc-api-steps-use.md)
+ [향상된 문서 속성을 사용자 지정 객체로 액세스](ddb-en-client-doc-api-convert.md)
+ [DynamoDB를 사용하지 `EnhancedDocument` 않고 사용](ddb-en-client-doc-api-standalone.md)

# 향상된 문서 API 사용 시작
<a name="ddb-en-client-doc-api-steps"></a>

향상된 문서 API에는 DynamoDB 향상된 클라이언트 API에 필요한 것과 동일한 [종속성](ddb-en-client-getting-started.md#ddb-en-client-gs-dep)이 필요합니다. 또한 이 항목의 시작 부분에 표시된 것처럼 [`DynamoDbEnhancedClient` 인스턴스](ddb-en-client-getting-started-dynamodbTable.md#ddb-en-client-getting-started-dynamodbTable-eclient)도 필요합니다.

Enhanced Document API는 버전 2.20.3과 함께 릴리스되었으므로 해당 버전 이상이 AWS SDK for Java 2.x필요합니다.

## `DocumentTableSchema` 및 `DynamoDbTable` 만들기
<a name="ddb-en-client-doc-api-steps-createschema"></a>

향상된 문서 API를 사용하여 DynamoDB 테이블에 대해 명령을 호출하려면 테이블을 클라이언트 측 [DynamoDbTable<EnhancedDocument>](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) 리소스 객체와 연결하세요.

향상된 클라이언트의 `table()` 메서드는 `DynamoDbTable<EnhancedDocument>` 인스턴스를 생성하고 DynamoDB 테이블 이름 및 `DocumentTableSchema`에 대한 파라미터를 필요로 합니다.

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)의 빌더에는 기본 인덱스 키와 하나 이상의 속성 변환기 제공자가 필요합니다. `AttributeConverterProvider.defaultProvider()` 메서드는 [기본 유형](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/package-summary.html)에 대한 변환기를 제공합니다. 사용자 지정 특성 변환기 공급자를 제공하는 경우에도 지정해야 합니다. 선택적인 보조 인덱스 키를 빌더에 추가할 수 있습니다.

다음 코드 조각은 스키마가 없는 `EnhancedDocument` 객체를 저장하는 `person` DynamoDB 테이블의 클라이언트 측 표현을 생성하는 코드를 보여줍니다.

```
DynamoDbTable<EnhancedDocument> documentDynamoDbTable = 
                enhancedClient.table("person",
                        TableSchema.documentSchemaBuilder()
                            // Specify the primary key attributes.
                            .addIndexPartitionKey(TableMetadata.primaryIndexName(),"id", AttributeValueType.S)
                            .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                            // Specify attribute converter providers. Minimally add the default one.
                            .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                            .build());
                                                         
// Call documentTable.createTable() if "person" does not exist in DynamoDB.
// createTable() should be called only one time.
```

다음은 이 단원 전체에서 사용되는 `person` 객체의 JSON 표현을 보여줍니다.

### JSON `person` 객체
<a name="ddb-en-client-doc-api-steps-createschema-obj"></a>

```
{
  "id": 1,
  "firstName": "Richard",
  "lastName": "Roe",
  "age": 25,
  "addresses":
    {
      "home": {
        "zipCode": "00000",
        "city": "Any Town",
        "state": "FL",
        "street": "123 Any Street"
      },
      "work": {
        "zipCode": "00001",
        "city": "Anywhere",
        "state": "FL",
        "street": "100 Main Street"
      }
    },
  "hobbies": [
    "Hobby 1",
    "Hobby 2"
  ],
  "phoneNumbers": [
    {
      "type": "Home",
      "number": "555-0100"
    },
    {
      "type": "Work",
      "number": "555-0119"
    }
  ]
}
```

# 향상된 문서 빌드
<a name="ddb-en-client-doc-api-steps-create-ed"></a>

`[EnhancedDocument](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html)`는 속성이 중첩된 복잡한 구조를 가진 문서 유형 객체를 나타냅니다. `EnhancedDocument`에는 `DocumentTableSchema`에 지정된 기본 키 속성과 일치하는 최상위 속성이 필요합니다. 나머지 내용은 임의적이며 최상위 속성과 깊이 중첩된 속성으로 구성될 수 있습니다.

요소를 추가하는 여러 가지 방법을 제공하는 빌더를 사용하여 `EnhancedDocument` 인스턴스를 만듭니다.

## JSON 문자열로 빌드
<a name="ddb-en-client-doc-api-steps-create-ed-fromJson"></a>

JSON 문자열을 사용하면 호출 한 번으로 `EnhancedDocument`을 만들 수 있습니다. 다음 코드 조각은 `jsonPerson()` 헬퍼 메서드에서 반환한 JSON 문자열에서 `EnhancedDocument`를 생성합니다. `jsonPerson()` 메서드는 이전에 표시된 [사람 객체](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema-obj)의 JSON 문자열 버전을 반환합니다.

```
EnhancedDocument document = 
        EnhancedDocument.builder()
                        .json( jsonPerson() )
                        .build());
```

## 개별 요소로 빌드
<a name="ddb-en-client-doc-api-steps-create-ed-fromparts"></a>

또는 빌더의 형식이 안전한 메서드를 사용하여 개별 구성 요소에서 `EnhancedDocument` 인스턴스를 빌드할 수 있습니다.

다음 예제는 이전 예제의 JSON 문자열로 빌드된 향상된 문서와 유사한 `person` 향상된 문서를 빌드합니다.

```
        /* Define the shape of an address map whose JSON representation looks like the following.
           Use 'addressMapEnhancedType' in the following EnhancedDocument.builder() to simplify the code.
           "home": {
             "zipCode": "00000",
             "city": "Any Town",
             "state": "FL",
             "street": "123 Any Street"
           }*/
        EnhancedType<Map<String, String>> addressMapEnhancedType =
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class));


        //  Use the builder's typesafe methods to add elements to the enhanced document.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                /* Add the map of addresses whose JSON representation looks like the following.
                        {
                          "home": {
                            "zipCode": "00000",
                            "city": "Any Town",
                            "state": "FL",
                            "street": "123 Any Street"
                          }
                        } */
                .putMap("addresses", getAddresses(), EnhancedType.of(String.class), addressMapEnhancedType)
                .putList("hobbies", List.of("Theater", "Golf"), EnhancedType.of(String.class))
                .build();
```

### 도우미 메서드
<a name="ddb-en-client-doc-api-steps-use-fromparts-helpers"></a>

```
    private static String phoneNumbersJSONString() {
        return "  [" +
                "    {" +
                "      \"type\": \"Home\"," +
                "      \"number\": \"555-0140\"" +
                "    }," +
                "    {" +
                "      \"type\": \"Work\"," +
                "      \"number\": \"555-0155\"" +
                "    }" +
                "  ]";
    }

    private static Map<String, Map<String, String>> getAddresses() {
        return Map.of(
                "home", Map.of(
                        "zipCode", "00002",
                        "city", "Any Town",
                        "state", "ME",
                        "street", "123 Any Street"));

    }
```

# CRUD 작업을 수행
<a name="ddb-en-client-doc-api-steps-use"></a>

`EnhancedDocument` 인스턴스를 정의한 후 DynamoDB 테이블에 저장할 수 있습니다. 다음 코드 조각은 개별 요소에서 생성된 [PersonDocument](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)를 사용합니다.

```
documentDynamoDbTable.putItem(personDocument);
```

DynamoDB에서 향상된 문서 인스턴스를 읽은 후에는 접근자를 사용하여 개별 속성 값을 추출할 수 있습니다. 다음 코드 조각은 `personDocument`에서 저장한 데이터에 액세스합니다. 또는 예제 코드의 마지막 부분에 표시된 대로 전체 콘텐츠를 JSON 문자열로 추출할 수 있습니다.

```
        // Read the item.
        EnhancedDocument personDocFromDb = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).build());

        // Access top-level attributes.
        logger.info("Name: {} {}", personDocFromDb.getString("firstName"), personDocFromDb.getString("lastName"));
        // Name: Shirley Rodriguez

        // Typesafe access of a deeply nested attribute. The addressMapEnhancedType shown previously defines the shape of an addresses map.
        Map<String, Map<String, String>> addresses = personDocFromDb.getMap("addresses", EnhancedType.of(String.class), addressMapEnhancedType);
        addresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));
        // {zipCode=00002, city=Any Town, street=123 Any Street, state=ME}

        // Alternatively, work with AttributeValue types checking along the way for deeply nested attributes.
        Map<String, AttributeValue> addressesMap = personDocFromDb.getMapOfUnknownType("addresses");
        addressesMap.keySet().forEach((String k) -> {
            logger.info("Looking at data for [{}] address", k);
            // Looking at data for [home] address
            AttributeValue value = addressesMap.get(k);
            AttributeValue cityValue = value.m().get("city");
            if (cityValue != null) {
                logger.info(cityValue.s());
                // Any Town
            }
        });

        List<AttributeValue> phoneNumbers = personDocFromDb.getListOfUnknownType("phoneNumbers");
        phoneNumbers.forEach((AttributeValue av) -> {
            if (av.hasM()) {
                AttributeValue type = av.m().get("type");
                if (type.s() != null) {
                    logger.info("Type of phone: {}", type.s());
                    // Type of phone: Home
                    // Type of phone: Work
                }
            }
        });

        String jsonPerson = personDocFromDb.toJson();
        logger.info(jsonPerson);
        // {"firstName":"Shirley","lastName":"Rodriguez","addresses":{"home":{"zipCode":"00002","city":"Any Town","street":"123 Any Street","state":"ME"}},"hobbies":["Theater","Golf"],
        //     "id":50,"nullAttribute":null,"age":53,"phoneNumbers":[{"number":"555-0140","type":"Home"},{"number":"555-0155","type":"Work"}]}
```

`EnhancedDocument` 인스턴스는 매핑된 데이터 클래스 대신 `[DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)`의 모든 메서드 또는 [DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html)와 함께 사용할 수 있습니다.

# 향상된 문서 속성을 사용자 지정 객체로 액세스
<a name="ddb-en-client-doc-api-convert"></a>

향상된 문서 API를 사용하면 스키마가 없는 구조의 속성을 읽고 쓸 수 있는 API를 제공할 뿐만 아니라 사용자 정의 클래스의 인스턴스 간에 속성을 변환할 수 있습니다.

향상된 문서 API는 DynamoDB 향상된 클라이언트 API의 일부로 [제어 속성 변환](ddb-en-client-adv-features-conversion.md) 단원에 표시된 `AttributeConverterProvider`와 `AttributeConverter`를 사용합니다.

다음 예제는 `CustomAttributeConverterProvider`를 중첩된 `AddressConverter` 클래스와 함께 사용하여 `Address` 객체를 변환합니다.

이 예제는 클래스의 데이터와 필요에 따라 빌드된 구조의 데이터를 혼합할 수 있음을 보여줍니다. 또한 이 예제는 사용자 정의 클래스가 중첩 구조의 모든 수준에서 사용될 수 있음을 보여줍니다. 이 예제의 `Address` 객체는 맵에서 사용되는 값입니다.

```
    public static void attributeToAddressClassMappingExample(DynamoDbEnhancedClient enhancedClient, DynamoDbClient standardClient) {
        String tableName = "customer";

        // Define the DynamoDbTable for an enhanced document.
        // The schema builder provides methods for attribute converter providers and keys.
        DynamoDbTable<EnhancedDocument> documentDynamoDbTable = enhancedClient.table(tableName,
                DocumentTableSchema.builder()
                        // Add the CustomAttributeConverterProvider along with the default when you build the table schema.
                        .attributeConverterProviders(
                                List.of(
                                        new CustomAttributeConverterProvider(),
                                        AttributeConverterProvider.defaultProvider()))
                        .addIndexPartitionKey(TableMetadata.primaryIndexName(), "id", AttributeValueType.N)
                        .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                        .build());
        // Create the DynamoDB table if needed.
        documentDynamoDbTable.createTable();
        waitForTableCreation(tableName, standardClient);


        // The getAddressesForCustomMappingExample() helper method that provides 'addresses' shows the use of a custom Address class
        // rather than using a Map<String, Map<String, String> to hold the address data.
        Map<String, Address> addresses = getAddressesForCustomMappingExample();

        // Build an EnhancedDocument instance to save an item with a mix of structures defined as needed and static classes.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                // Note the use of 'EnhancedType.of(Address.class)' instead of the more generic
                // 'EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class))' that was used in a previous example.
                .putMap("addresses", addresses, EnhancedType.of(String.class), EnhancedType.of(Address.class))
                .putList("hobbies", List.of("Hobby 1", "Hobby 2"), EnhancedType.of(String.class))
                .build();
        // Save the item to DynamoDB.
        documentDynamoDbTable.putItem(personDocument);

        // Retrieve the item just saved.
        EnhancedDocument srPerson = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).sortValue("Rodriguez").build());

        // Access the addresses attribute.
        Map<String, Address> srAddresses = srPerson.get("addresses",
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(Address.class)));

        srAddresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));

        documentDynamoDbTable.deleteTable();

// The content logged to the console shows that the saved maps were converted to Address instances.
Address{street='123 Main Street', city='Any Town', state='NC', zipCode='00000'}
Address{street='100 Any Street', city='Any Town', state='NC', zipCode='00000'}
```

## `CustomAttributeConverterProvider` 코드
<a name="ddb-en-client-doc-api-convert-provider"></a>

```
public class CustomAttributeConverterProvider implements AttributeConverterProvider {

    private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
            // 1. Add AddressConverter to the internal cache.
            EnhancedType.of(Address.class), new AddressConverter());

    public static CustomAttributeConverterProvider create() {
        return new CustomAttributeConverterProvider();
    }

    // 2. The enhanced client queries the provider for attribute converters if it
    //    encounters a type that it does not know how to convert.
    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
        return (AttributeConverter<T>) converterCache.get(enhancedType);
    }

    // 3. Custom attribute converter
    private class AddressConverter implements AttributeConverter<Address> {
        // 4. Transform an Address object into a DynamoDB map.
        @Override
        public AttributeValue transformFrom(Address address) {

            Map<String, AttributeValue> attributeValueMap = Map.of(
                    "street", AttributeValue.fromS(address.getStreet()),
                    "city", AttributeValue.fromS(address.getCity()),
                    "state", AttributeValue.fromS(address.getState()),
                    "zipCode", AttributeValue.fromS(address.getZipCode()));

            return AttributeValue.fromM(attributeValueMap);
        }

        // 5. Transform the DynamoDB map attribute to an Address oject.
        @Override
        public Address transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> m = attributeValue.m();
            Address address = new Address();
            address.setStreet(m.get("street").s());
            address.setCity(m.get("city").s());
            address.setState(m.get("state").s());
            address.setZipCode(m.get("zipCode").s());

            return address;
        }

        @Override
        public EnhancedType<Address> type() {
            return EnhancedType.of(Address.class);
        }

        @Override
        public AttributeValueType attributeValueType() {
            return AttributeValueType.M;
        }
    }
}
```

## `Address` 클래스
<a name="ddb-en-client-doc-api-convert-address"></a>

```
public class Address {
                  private String street;
                  private String city;
                  private String state;
                  private String zipCode;

                  public Address() {
                  }

                  public String getStreet() {
                  return this.street;
                  }

                  public String getCity() {
                  return this.city;
                  }

                  public String getState() {
                  return this.state;
                  }

                  public String getZipCode() {
                  return this.zipCode;
                  }

                  public void setStreet(String street) {
                  this.street = street;
                  }

                  public void setCity(String city) {
                  this.city = city;
                  }

                  public void setState(String state) {
                  this.state = state;
                  }

                  public void setZipCode(String zipCode) {
                  this.zipCode = zipCode;
                  }
                  }
```

## 주소를 제공하는 도우미 메서드
<a name="ddb-en-client-doc-api-convert-helper"></a>

다음 도우미 메서드는 값에 대한 일반 `Map<String, String>` 인스턴스가 아닌 사용자 지정 `Address` 인스턴스를 값에 사용하는 맵을 제공합니다.

```
    private static Map<String, Address> getAddressesForCustomMappingExample() {
        Address homeAddress = new Address();
        homeAddress.setStreet("100 Any Street");
        homeAddress.setCity("Any Town");
        homeAddress.setState("NC");
        homeAddress.setZipCode("00000");

        Address workAddress = new Address();
        workAddress.setStreet("123 Main Street");
        workAddress.setCity("Any Town");
        workAddress.setState("NC");
        workAddress.setZipCode("00000");

        return Map.of("home", homeAddress,
                "work", workAddress);
    }
```

# DynamoDB를 사용하지 `EnhancedDocument` 않고 사용
<a name="ddb-en-client-doc-api-standalone"></a>

일반적으로 `EnhancedDocument`의 인스턴스를 사용하여 문서 유형 DynamoDB 항목을 읽고 쓰지만 DynamoDB와 독립적으로 사용할 수도 있습니다.

`EnhancedDocuments`을 사용하면 JSON 문자열이나 사용자 지정 객체를 다음 예제와 같은 `AttributeValues`의 하위 수준 맵으로 변환하는 기능을 사용할 수 있습니다.

```
    public static void conversionWithoutDynamoDbExample() {
        Address address = new Address();
        address.setCity("my city");
        address.setState("my state");
        address.setStreet("my street");
        address.setZipCode("00000");

        // Build an EnhancedDocument instance for its conversion functionality alone.
        EnhancedDocument addressEnhancedDoc = EnhancedDocument.builder()
                // Important: You must specify attribute converter providers when you build an EnhancedDocument instance not used with a DynamoDB table.
                .attributeConverterProviders(new CustomAttributeConverterProvider(), DefaultAttributeConverterProvider.create())
                .put("addressDoc", address, Address.class)
                .build();

        // Convert address to a low-level item representation.
        final Map<String, AttributeValue> addressAsAttributeMap = addressEnhancedDoc.getMapOfUnknownType("addressDoc");
        logger.info("addressAsAttributeMap: {}", addressAsAttributeMap.toString());

        // Convert address to a JSON string.
        String addressAsJsonString = addressEnhancedDoc.getJson("addressDoc");
        logger.info("addressAsJsonString: {}", addressAsJsonString);
        // Convert addressEnhancedDoc back to an Address instance.
        Address addressConverted =  addressEnhancedDoc.get("addressDoc", Address.class);
        logger.info("addressConverted: {}", addressConverted.toString());
    }

   /* Console output:
          addressAsAttributeMap: {zipCode=AttributeValue(S=00000), state=AttributeValue(S=my state), street=AttributeValue(S=my street), city=AttributeValue(S=my city)}
          addressAsJsonString: {"zipCode":"00000","state":"my state","street":"my street","city":"my city"}
          addressConverted: Address{street='my street', city='my city', state='my state', zipCode='00000'}
   */
```

**참고**  
DynamoDB 테이블과 별개로 향상된 문서를 사용하는 경우 빌더에서 속성 변환기 공급자를 명시적으로 설정했는지 확인하세요.  
반대로 문서 테이블 스키마는 향상된 문서가 DynamoDB 테이블과 함께 사용될 때 변환기 제공자에게 제공합니다.

# 확장 프로그램을 사용하여 DynamoDB 향상된 클라이언트 작업 사용자 지정
<a name="ddb-en-client-extensions"></a>

DynamoDB 향상된 클라이언트 API는 매핑 작업 이외의 기능을 제공하는 플러그인 확장을 지원합니다. 확장 프로그램은 2가지 후크 메서드를 사용하여 읽기 및 쓰기 작업 중에 데이터를 수정합니다.
+ `beforeWrite()` - 쓰기 작업이 발생하기 전에 수정합니다.
+ `afterRead()` - 읽기 작업이 발생한 후 결과를 수정합니다.

일부 작업(예: 항목 업데이트)은 쓰기와 읽기를 차례로 모두 수행하므로 두 후크 메서드가 모두 직접 호출됩니다.

## 확장 프로그램 로드 방법
<a name="ddb-en-client-extensions-loading"></a>

확장 프로그램은 향상된 클라이언트 빌더에 지정된 순서대로 로드됩니다. 하나의 확장이 이전 확장에 의해 변환된 값에 대해 작동할 수 있으므로 로드 순서가 중요할 수 있습니다.

기본적으로 향상된 클라이언트는 2개의 확장 프로그램을 로드합니다.
+ `[VersionedRecordExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.html)` - 낙관적 잠금 제공
+ `[AtomicCounterExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/AtomicCounterExtension.html)` - 카운터 속성을 자동으로 증분

향상된 클라이언트 빌더로 기본 동작을 재정의하고 모든 확장 프로그램을 로드할 수 있습니다. 기본 확장자를 원하지 않는 경우에는 none을 지정할 수도 있습니다.

**중요**  
자체 확장을 로드하는 경우 확장 클라이언트는 기본 확장을 로드하지 않습니다. 기본 확장 중 하나가 제공하는 동작을 원하는 경우 해당 확장을 확장 목록에 명시적으로 추가해야 합니다.

다음 예제에서는 `VersionedRecordExtension` 이후 이름이 `verifyChecksumExtension`인 사용자 지정 확장 프로그램을 로드하는 방법을 보여줍니다. 이 예제에서는 `AtomicCounterExtension`가 로드되지 않습니다.

```
DynamoDbEnhancedClientExtension versionedRecordExtension = VersionedRecordExtension.builder().build();

DynamoDbEnhancedClient enhancedClient = 
    DynamoDbEnhancedClient.builder()
                          .dynamoDbClient(dynamoDbClient)
                          .extensions(versionedRecordExtension, verifyChecksumExtension)
                          .build();
```

## 사용 가능한 확장 프로그램 세부 정보 및 구성
<a name="ddb-en-client-extensions-details"></a>

다음 섹션에서는 SDK에서 사용 가능한 각 확장 프로그램에 대한 자세한 정보를 제공합니다.

### `VersionedRecordExtension`을 사용한 낙관적 잠금 구현
<a name="ddb-en-client-extensions-VRE"></a>

`VersionedRecordExtension` 확장 프로그램은 항목이 데이터베이스에 작성될 때 항목 버전 번호를 증분하고 추적하여 낙관적 잠금을 제공합니다. 실제 지속 항목의 버전 번호가 애플리케이션이 마지막으로 읽은 값과 일치하지 않는 경우 쓰기가 실패하도록 하는 조건이 모든 쓰기에 추가됩니다.

#### 구성
<a name="ddb-en-client-extensions-VRE-conf"></a>

항목 버전 번호를 추적하는 데 사용할 속성을 지정하려면 테이블 스키마에서 숫자 속성에 태그를 지정하세요.

다음 코드 조각은 `version` 속성에 항목 버전 번호가 포함되도록 지정합니다.

```
    @DynamoDbVersionAttribute
    public Integer getVersion() {...};
    public void setVersion(Integer version) {...};
```

동일한 정적 테이블 스키마 접근 방식이 다음 코드 조각에 나와 있습니다.

```
    .addAttribute(Integer.class, a -> a.name("version")
                                       .getter(Customer::getVersion)
                                       .setter(Customer::setVersion)
                                        // Apply the 'version' tag to the attribute.
                                       .tags(VersionedRecordExtension.AttributeTags.versionAttribute())
```

#### 작동 방식
<a name="ddb-en-client-extensions-VRE-how-it-works"></a>

`VersionedRecordExtension`이 있는 낙관적 잠금 전략은 이러한 `DynamoDbEnhancedClient` 및 `DynamoDbTable` 메서드에 다음과 같은 영향을 끼칩니다.

**`putItem`**  
새 항목에는 초기 버전 값 0이 할당됩니다. `@DynamoDbVersionAttribute(startAt = X)`로 구성할 수 있습니다.

**`updateItem`**  
이후 항목을 검색하여 속성을 하나 이상 업데이트한 후 변경 사항을 저장하려고 해도 클라이언트 측과 서버 측의 버전 번호가 일치해야만 작업이 완료됩니다.  
작업이 완료되면 버전 번호가 자동으로 1씩 증분됩니다. `@DynamoDbVersionAttribute(incrementBy = X)`로 구성할 수 있습니다.

**`deleteItem`**  
`DynamoDbVersionAttribute` 주석은 영향을 미치지 않습니다. 항목을 삭제할 때 조건 표현식을 수동으로 추가해야 합니다.  
다음 예제에서는 조건식을 추가하여 삭제된 항목이 읽은 항목인지 확인합니다. 다음 예제(`recordVersion`)에서는 `@DynamoDbVersionAttribute` 주석이 달린 bean의 속성입니다.  

```
// 1. Read the item and get its current version.
Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());
// `recordVersion` is the bean's attribute that is annotated with `@DynamoDbVersionAttribute`.
AttributeValue currentVersion = item.getRecordVersion();

// 2. Create conditional delete with the `currentVersion` value.
DeleteItemEnhancedRequest deleteItemRequest =
    DeleteItemEnhancedRequest.builder()
       .key(KEY)
       .conditionExpression(Expression.builder()
           .expression("recordVersion = :current_version_value")
           .putExpressionValue(":current_version_value", currentVersion)
           .build()).build();

customerTable.deleteItem(deleteItemRequest);
```

**`transactWriteItems`**  
+ `addPutItem`: 이 메서드는 `putItem`과 동일한 동작을 갖습니다.
+ `addUpdateItem`: 이 메서드는 `updateItem`과 동일한 동작을 갖습니다.
+ `addDeleteItem`: 이 메서드는 `deleteItem`과 동일한 동작을 갖습니다.

**`batchWriteItem`**  
+ `addPutItem`: 이 메서드는 `putItem`과 동일한 동작을 갖습니다.
+ `addDeleteItem`: 이 메서드는 `deleteItem`과 동일한 동작을 갖습니다.

**참고**  
DynamoDB 전역 테이블은 동시 업데이트 간에 [‘최종 라이터 우선 적용’ 조정](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes)을 사용하며, DynamoDB는 최종 라이터를 확인하기 위해 노력합니다. 전역 테이블을 사용하는 경우 이 '최종 라이터 우선 적용' 정책은 모든 복제본이 결국 DynamoDB에서 결정한 최종 쓰기를 기반으로 수렴되므로 잠금 전략이 예상대로 작동하지 않을 수 있음을 의미합니다.

#### 사용 해제 방법
<a name="ddb-en-client-extensions-VRE-how-to-disable"></a>

낙관적 잠금을 사용 해제하려면 `@DynamoDbVersionAttribute` 주석을 사용하지 않습니다.

### `AtomicCounterExtension`을 사용하여 카운터 구현
<a name="ddb-en-client-extensions-ACE"></a>

`AtomicCounterExtension` 확장 프로그램은 레코드가 데이터베이스에 기록될 때마다 태그가 지정된 숫자 속성을 증분시킵니다. 시작 및 증분 값을 지정할 수 있습니다. 값을 지정하지 않으면 시작 값은 0으로 설정되고 속성 값은 1씩 증가합니다.

#### 구성
<a name="ddb-en-client-extensions-ACE-conf"></a>

어떤 속성이 카운터인지 지정하려면 테이블 스키마에서 `Long` 유형의 속성에 태그를 지정하세요.

다음 코드 조각은 `counter` 속성의 기본 시작 및 증분 값 사용을 보여줍니다.

```
    @DynamoDbAtomicCounter
    public Long getCounter() {...};
    public void setCounter(Long counter) {...};
```

정적 테이블 스키마 접근 방식이 다음 코드 조각에 나와 있습니다. 원자 카운터 확장은 시작 값 10을 사용하고 레코드가 기록될 때마다 값을 5씩 증가시킵니다.

```
    .addAttribute(Integer.class, a -> a.name("counter")
                                       .getter(Customer::getCounter)
                                       .setter(Customer::setCounter)
                                        // Apply the 'atomicCounter' tag to the attribute with start and increment values.
                                       .tags(StaticAttributeTags.atomicCounter(10L, 5L))
```

### `AutoGeneratedTimestampRecordExtension`을 사용하여 타임스탬프 추가
<a name="ddb-en-client-extensions-AGTE"></a>

`AutoGeneratedTimestampRecordExtension` 확장 프로그램은 항목이 데이터베이스에 성공적으로 기록될 때마다 `[Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html)` 유형의 태그가 지정된 속성을 현재 타임스탬프로 자동 업데이트합니다. 이 확장은 기본적으로 로드되지 않습니다.

#### 구성
<a name="ddb-en-client-extensions-AGTE-conf"></a>

현재 타임스탬프로 업데이트할 속성을 지정하려면 테이블 스키마에서 `Instant` 속성에 태그를 지정하세요.

`lastUpdate` 속성은 다음 코드 조각에서 확장 프로그램의 동작 대상입니다. 속성이 `Instant` 유형이어야 한다는 요구 사항에 유의하세요.

```
    @DynamoDbAutoGeneratedTimestampAttribute
    public Instant getLastUpdate() {...}
    public void setLastUpdate(Instant lastUpdate) {...}
```

동일한 정적 테이블 스키마 접근 방식이 다음 코드 조각에 나와 있습니다.

```
     .addAttribute(Instant.class, a -> a.name("lastUpdate")
                                        .getter(Customer::getLastUpdate)
                                        .setter(Customer::setLastUpdate)
                                        // Applying the 'autoGeneratedTimestamp' tag to the attribute.
                                        .tags(AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute())
```

### AutoGeneratedUuidExtension을 사용하여 UUID 생성
<a name="ddb-en-client-extensions-AGUE"></a>

`AutoGeneratedUuidExtension` 확장 프로그램은 새 레코드가 데이터베이스에 기록될 때 속성에 대한 고유한 UUID(범용 고유 식별자)를 생성합니다. Java JDK [UUID.randomUUID()](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#randomUUID--) 메서드를 사용하고 `java.lang.String` 유형의 속성에 적용됩니다. 이 확장은 기본적으로 로드되지 않습니다.

#### 구성
<a name="ddb-en-client-extensions-AGUE-conf"></a>

`uniqueId` 속성은 다음 코드 조각에서 확장 프로그램의 동작 대상입니다.

```
    @AutoGeneratedUuidExtension
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

동일한 정적 테이블 스키마 접근 방식이 다음 코드 조각에 나와 있습니다.

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
```

확장 프로그램이 `putItem` 메서드에 한해 UUID를 채우고 `updateItem` 메서드에는 채우지 않도록 하려면 다음 코드 조각과 같이 [업데이트 동작](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html) 주석을 추가합니다.

```
    @AutoGeneratedUuidExtension
    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

정적 테이블 스키마 접근 방식을 사용하는 경우 다음과 같은 동등한 코드를 사용합니다.

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(),
                                              StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))
```

# 사용자 지정 확장 프로그램 예제
<a name="ddb-en-client-extensions-custom"></a>

`DynamoDbEnhancedClientExtension` 인터페이스를 구현하여 사용자 지정 확장 프로그램을 만들 수 있습니다. 다음 사용자 지정 확장 클래스는 데이터베이스의 항목에 아직 속성이 없는 경우 업데이트 표현식을 사용하여 `registrationDate` 속성을 설정하는 `beforeWrite()` 메서드를 보여줍니다.

```
public final class CustomExtension implements DynamoDbEnhancedClientExtension {

    // 1. In a custom extension, use an UpdateExpression to define what action to take before
    //    an item is updated.
    @Override
    public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
        if ( context.operationContext().tableName().equals("Customer")
                && context.operationName().equals(OperationName.UPDATE_ITEM)) {
            return WriteModification.builder()
                    .updateExpression(createUpdateExpression())
                    .build();
        }
        return WriteModification.builder().build();  // Return an "empty" WriteModification instance if the extension should not be applied.
                                                     // In this case, if the code is not updating an item on the Customer table.
    }

    private static UpdateExpression createUpdateExpression() {

        // 2. Use a SetAction, a subclass of UpdateAction, to provide the values in the update.
        SetAction setAction =
                SetAction.builder()
                        .path("registrationDate")
                        .value("if_not_exists(registrationDate, :regValue)")
                        .putExpressionValue(":regValue", AttributeValue.fromS(Instant.now().toString()))
                        .build();
        // 3. Build the UpdateExpression with one or more UpdateAction.
        return UpdateExpression.builder()
                .addAction(setAction)
                .build();
    }
}
```

# 비동기식으로 DynamoDB 향상된 클라이언트 API 사용
<a name="ddb-en-client-async"></a>

애플리케이션에 DynamoDB에 대한 비차단 비동기 호출이 필요한 경우 [DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html)를 사용할 수 있습니다. 동기 구현과 유사하지만 다음과 같은 주요 차이점이 있습니다.

1. `DynamoDbEnhancedAsyncClient`를 빌드할 때는 다음 코드 조각과 같이 표준 클라이언트의 비동기 버전 `DynamoDbAsyncClient`을 제공해야 합니다.

   ```
    DynamoDbEnhancedAsyncClient enhancedClient = 
        DynamoDbEnhancedAsyncClient.builder()
                                   .dynamoDbClient(dynamoDbAsyncClient)
                                   .build();
   ```

1. 단일 데이터 객체를 반환하는 메서드는 결과만 반환하는 대신 결과의 `CompletableFuture`를 반환합니다. 그러면 애플리케이션은 결과를 차단하지 않고도 다른 작업을 수행할 수 있습니다. 다음 코드 조각은 비동기 `getItem()` 메서드를 보여줍니다.

   ```
   CompletableFuture<Customer> result = customerDynamoDbTable.getItem(customer);
   // Perform other work here.
   return result.join();   // Now block and wait for the result.
   ```

1. 페이지별로 구분된 결과 목록을 반환하는 메서드는 동일한 메서드에 대해 동기식 `DynamoDbEnhanceClient`를 반환하는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html) 대신 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html)를 반환합니다. 그런 다음 애플리케이션은 해당 게시자에 대한 핸들러를 구독하여 차단할 필요 없이 결과를 비동기적으로 처리할 수 있습니다.

   ```
   PagePublisher<Customer> results = customerDynamoDbTable.query(r -> r.queryConditional(keyEqualTo(k -> k.partitionValue("Smith"))));
   results.subscribe(myCustomerResultsProcessor);
   // Perform other work and let the processor handle the results asynchronously.
   ```

   `SdkPublisher API`를 사용한 보다 완전한 예제를 보려면 이 가이드의 비동기 `scan()`메서드를 설명하는 단원의 [예제](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-scan-async)를 참조하세요.

# 데이터 클래스 주석
<a name="ddb-en-client-anno-index"></a>

다음 표에는 데이터 클래스에 사용할 수 있는 주석이 나열되어 있으며 이 가이드의 정보 및 예제에 대한 링크가 제공됩니다. 테이블은 주석 이름을 기준으로 알파벳 오름차순으로 정렬됩니다.


**이 가이드에 사용된 데이터 클래스 주석**  

| 주석 이름 | 주석은 1에 적용됩니다. | 하는 일 | 이 가이드에 표시된 위치 | 
| --- | --- | --- | --- | 
| DynamoDbAtomicCounter | 속성2 | 레코드가 데이터베이스에 기록될 때마다 태그가 지정된 숫자 속성이 증가합니다. | [소개 및 토론.](ddb-en-client-extensions.md#ddb-en-client-extensions-ACE) | 
| DynamoDbAttribute | 속성 | DynamoDB 테이블 속성에 매핑되는 Bean 속성을 정의하거나 이름을 바꿉니다. |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbAutoGeneratedTimestampAttribute | 속성 | 항목이 데이터베이스에 성공적으로 기록될 때마다 현재 타임스탬프로 태그가 지정된 속성을 업데이트합니다. | [소개 및 토론](ddb-en-client-extensions.md#ddb-en-client-extensions-AGTE). | 
| DynamoDbAutoGeneratedUuid | 속성 | 새 레코드가 데이터베이스에 기록될 때 속성에 대한 고유한 UUID(범용 고유 식별자)를 생성합니다. | [소개 및 토론.](ddb-en-client-extensions.md#ddb-en-client-extensions-AGUE) | 
| DynamoDbBean | class | 데이터 클래스를 테이블 스키마에 매핑할 수 있는 것으로 표시합니다. | 먼저 시작하기 단원의 [Customer 클래스](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)에서 사용하세요. 가이드 곳곳에 여러 가지 사용법이 나와 있습니다. | 
| DynamoDbConvertedBy | 속성 | 사용자 지정 AttributeConverter을 주석이 달린 속성과 연결합니다. | [초기 논의 및 예제.](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-single) | 
| DynamoDbFlatten | 속성 | 개별 DynamoDB 데이터 클래스의 모든 속성을 평면화하여 데이터베이스에서 읽고 쓰는 레코드에 최상위 속성으로 추가합니다. |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnore | 속성 |  그 결과 속성이 매핑되지 않은 상태로 남습니다.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnoreNulls | 속성 | 중첩된 DynamoDB 객체의 null 속성이 저장되지 않도록 합니다. | [설명 및 예제.](ddb-en-client-adv-features-ignore-null.md) | 
| DynamoDbImmutable | class |  변경할 수 없는 데이터 클래스를 테이블 스키마에 매핑 가능한 것으로 표시합니다.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPartitionKey | 속성 |  속성을 DynamoDb 테이블의 기본 파티션 키(해시 키)로 표시합니다.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPreserveEmptyObject | 속성 |  주석이 달린 속성에 매핑된 개체에 대한 데이터가 없는 경우 개체가 모든 null 필드로 초기화되어야 함을 지정합니다.  | [설명 및 예제.](ddb-en-client-adv-features-empty.md) | 
| DynamoDbSecondaryPartitionKey | 속성 |  속성을 글로벌 보조 인텍스의 파티션 키로 표시합니다.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSecondarySortKey | 속성 |  속성을 글로벌 또는 로컬 보조 인덱스의 선택적 정렬 키로 표시합니다.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSortKey | 속성 |  속성을 선택적 기본 정렬 키(범위 키)로 표시합니다.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbUpdateBehavior | 속성 |  UpdateItem과 같은 '업데이트' 작업의 일부로 이 속성이 업데이트될 때의 동작을 지정합니다.  | [소개 및 예제.](ddb-en-client-adv-features-upd-behavior.md) | 
| DynamoDbVersionAttribute | 속성 | 항목 버전 번호를 증가시킵니다. | [소개 및 토론.](ddb-en-client-extensions.md#ddb-en-client-extensions-VRE) | 

1속성 수준 주석을 getter 또는 setter에 적용할 수 있지만 둘 다 적용할 수는 없습니다. 이 가이드에서는 getter에 대한 주석을 보여줍니다.

2`property` 용어는 일반적으로 JavaBean 데이터 클래스에서 캡슐화된 값에 사용됩니다. 하지만 이 가이드에서는 DynamoDB에서 사용하는 용어와의 일관성을 위해 용어 `attribute`를 대신 사용합니다.