

# AWS SDK for Java 2.x를 사용한 DynamoDB 프로그래밍
<a name="ProgrammingWithJava"></a>

이 프로그래밍 안내서는 Java를 통해 Amazon DynamoDB를 사용하려는 프로그래머에게 지침을 제공합니다. 이 안내서는 추상화 계층, 구성 관리, 오류 처리, 재시도 정책 제어, 연결 유지 관리와 같은 다양한 개념을 다룹니다.

**Topics**
+ [AWS SDK for Java 2.x 소개](#AboutProgrammingWithJavaSDK)
+ [시작하기](#GetStartedProgrammingWithJavaSDK)
+ [SDK for Java 2.x 설명서](#ProgrammingWithJavaUseDoc)
+ [지원되는 인터페이스](#JavaInterfaces)
+ [추가 코드 예시](#AdditionalCodeEx)
+ [동기식 및 비동기식 프로그래밍](#SyncAsyncProgramming)
+ [HTTP 클라이언트](#HttpClients)
+ [구성](#ConfigHttpClient)
+ [오류 처리](#JavaErrorHandling)
+ [AWS 요청 ID](#JavaRequestID)
+ [로깅](#JavaLogging)
+ [페이지 매김](#JavaPagination)
+ [데이터 클래스 주석](#JavaDataClassAnnotation)

## AWS SDK for Java 2.x 소개
<a name="AboutProgrammingWithJavaSDK"></a>

공식 AWS SDK for Java를 사용하여 Java에서 DynamoDB에 액세스할 수 있습니다. SDK for Java에는 1.x와 2.x의 두 가지 버전이 있습니다. 1.x의 경우 2024년 1월 12일에 지원 종료가 [발표되었습니다](https://aws.amazon.com/blogs/developer/announcing-end-of-support-for-aws-sdk-for-java-v1-x-on-december-31-2025/). 2024년 7월 31일에 유지 관리 모드로 전환될 계획이며, 2025년 12월 31일에 지원이 종료될 예정입니다. 새 개발의 경우 2018년에 처음 릴리스된 2.x를 사용하는 것이 좋습니다. 이 안내서는 2.x만을 대상으로 하며 SDK에서 DynamoDB와 관련된 부분에만 초점을 맞춥니다.

AWS SDK에 대한 유지 관리 및 지원에 대한 자세한 내용은 **AWS SDK 및 도구 참조 안내서의 [AWS SDK and Tools maintenance policy](https://docs.aws.amazon.com/sdkref/latest/guide/maint-policy.html) 및 [AWS SDKs and Tools version support matrix](https://docs.aws.amazon.com/sdkref/latest/guide/version-support-matrix.html)를 참조하세요.

AWS SDK for Java 2.x에서 1.x 코드 베이스 상당수를 다시 작성했습니다. SDK for Java 2.x는 Java 8에 도입된 비차단 I/O와 같은 최신 Java 기능을 지원합니다. 또한, SDK for Java 2.x는 플러그형 HTTP 클라이언트 구현에 대한 지원을 추가하여 보다 많은 네트워크 연결 유연성과 구성 옵션을 제공합니다.

SDK for Java 1.x와 비교할 때 SDK for Java 2.x의 눈에 띄는 변화는 새로운 패키지 이름 사용입니다. Java 1.x SDK는 `com.amazonaws` 패키지 이름을 사용하는 반면 Java 2.x SDK는 `software.amazon.awssdk` 패키지 이름을 사용합니다. 마찬가지로 Java 1.x SDK용 Maven 아티팩트는 `com.amazonaws` `groupId`를 사용하는 반면, Java 2.x SDK 아티팩트는 `software.amazon.awssdk` `groupId`를 사용합니다.

**중요**  
AWS SDK for Java 1.x에는 이름이 `com.amazonaws.dynamodbv2`인 DynamoDB 패키지가 있습니다. 패키지 이름의 'v2'는 해당 패키지가 Java 2(J2SE) 용이라는 의미가 아닙니다. 'v2'는 패키지가 하위 수준 API의 [원래 버전](Appendix.APIv20111205.md) 대신 DynamoDB 하위 수준 API의 [두 번째 버전](CurrentAPI.md)을 지원함을 나타냅니다.

### Java 버전 지원
<a name="SupportedJavaVersions"></a>

AWS SDK for Java 2.x는 장기 지원(LTS) [Java 릴리스](https://github.com/aws/aws-sdk-java-v2?tab=readme-ov-file#maintenance-and-support-for-java-versions)를 모두 지원합니다.

## AWS SDK for Java 2.x 시작하기
<a name="GetStartedProgrammingWithJavaSDK"></a>

다음 자습서에서는 [Apache Maven](https://maven.apache.org/)을 사용하여 SDK for Java 2.x에 대한 종속성을 정의하는 방법을 보여줍니다. 또한 이 자습서에서는 사용 가능한 DynamoDB 테이블을 나열하기 위해 DynamoDB에 연결하는 코드를 작성하는 방법도 보여줍니다. 이 안내서의 자습서는 **AWS SDK for Java 2.x 개발자 안내서에 있는 [Get started with the AWS SDK for Java 2.x](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html) 자습서를 기반으로 합니다. Amazon S3 대신 DynamoDB를 직접 호출하도록 이 자습서를 편집했습니다.

**Topics**
+ [1단계: 튜토리얼 설정](#GetStartedJavaSetup)
+ [2단계: 프로젝트 생성](#GetStartedJavaProjectSetup)
+ [3단계: 코드 작성](#GetStartedJavaCode)
+ [4단계: 애플리케이션 빌드 및 실행](#GetStartedRunJava)

### 1단계: 튜토리얼 설정
<a name="GetStartedJavaSetup"></a>

이 튜토리얼을 시작하기 전에 다음이 필요합니다.
+ DynamoDB에 액세스할 수 있는 권한
+ AWS 액세스 포털을 사용하여 AWS 서비스에 대한 SSO(Single Sign-On) 액세스로 구성된 Java 개발 환경

이 자습서를 설정하려면 **AWS SDK for Java 2.x 개발자 안내서의 [설정 개요](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/setup.html#setup-overview)에 있는 지침을 따르세요. Java SDK에 대한 [SSO(Single Sign-On) 액세스로 개발 환경을 구성](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/setup.html#setup-credentials)하고 [활성 AWS 액세스 포털 세션](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/setup.html#setup-login-sso)이 있는 경우 이 자습서의 [2단계](#GetStartedJavaProjectSetup)로 계속 진행합니다.

### 2단계: 프로젝트 생성
<a name="GetStartedJavaProjectSetup"></a>

이 자습서의 프로젝트를 생성하려면 프로젝트 구성 방법에 대한 입력을 요청하는 Maven 명령을 실행합니다. 모든 입력이 입력되고 확인되면 Maven은 `pom.xml` 파일을 생성하여 프로젝트 빌드를 완료하고 스텁 Java 파일을 생성합니다.

1. 터미널 또는 명령 프롬프트 창을 열고 원하는 디렉터리 (예: `Desktop` 또는 `Home` 폴더)로 이동합니다.

1. 터미널에서 다음 명령을 입력하고 **Enter** 키를 누릅니다.

   ```
   mvn archetype:generate \
      -DarchetypeGroupId=software.amazon.awssdk \
      -DarchetypeArtifactId=archetype-app-quickstart \
      -DarchetypeVersion=2.22.0
   ```

1. 각 프롬프트의 두 번째 열에 나열된 값을 입력합니다.    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/ProgrammingWithJava.html)

1. 마지막 값을 입력하면 Maven에서 선택한 항목을 나열합니다. 확인하려면 **Y**를 입력합니다. 아니면 **N**을 입력한 다음 선택 사항을 다시 입력합니다.

Maven은 입력한 `artifactId` 값을 기반으로 이름이 `getstarted`로 지정된 프로젝트 폴더를 만듭니다. `getstarted` 폴더 안에서 검토할 수 있는 `README.md`라는 이름의 파일, `pom.xml` 파일, `src` 디렉터리를 찾습니다.

Maven은 다음과 같은 디렉터리 트리를 만듭니다.

```
getstarted
 ├── README.md
 ├── pom.xml
 └── src
     ├── main
     │   ├── java
     │   │   └── org
     │   │       └── example
     │   │           ├── App.java
     │   │           ├── DependencyFactory.java
     │   │           └── Handler.java
     │   └── resources
     │       └── simplelogger.properties
     └── test
         └── java
             └── org
                 └── example
                     └── HandlerTest.java
 
 10 directories, 7 files
```

다음은 `pom.xml` 프로젝트 파일의 콘텐츠를 보여줍니다.

#### `pom.xml`
<a name="ProjectSetupCollapse2"></a>

`dependencyManagement` 섹션은 AWS SDK for Java 2.x에 대한 종속성을 포함하며 `dependencies` 섹션에는 DynamoDB에 대한 종속성이 있습니다. 이러한 종속성을 지정하면 Maven이 관련 `.jar` 파일을 Java 클래스 경로에 포함하도록 강제합니다. 기본적으로 AWS SDK에는 모든 AWS 서비스에 대한 클래스가 모두 포함되어 있지는 않습니다. DynamoDB의 경우 하위 수준 인터페이스를 사용하면 `dynamodb` 아티팩트에 대한 종속성이 있어야 합니다. 또는 상위 수준 인터페이스를 사용하는 경우 `dynamodb-enhanced` 아티팩트에 종속적이어야 합니다 관련 종속성을 포함하지 않으면 코드가 컴파일되지 않습니다. 프로젝트는 `maven.compiler.source` 및 `maven.compiler.target` 속성의 `1.8` 값 때문에 Java 1.8을 사용합니다.

```
<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>org.example</groupId>
     <artifactId>getstarted</artifactId>
     <version>1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
         <maven.shade.plugin.version>3.2.1</maven.shade.plugin.version>
         <maven.compiler.plugin.version>3.6.1</maven.compiler.plugin.version>
         <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
         <aws.java.sdk.version>2.22.0</aws.java.sdk.version> <-------- SDK version picked up from archetype version.
         <slf4j.version>1.7.28</slf4j.version>
         <junit5.version>5.8.1</junit5.version>
     </properties>
 
     <dependencyManagement>
         <dependencies>
             <dependency>
                 <groupId>software.amazon.awssdk</groupId>
                 <artifactId>bom</artifactId>
                 <version>${aws.java.sdk.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
         </dependencies>
     </dependencyManagement>
 
     <dependencies>
         <dependency>
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>dynamodb</artifactId>  <-------- DynamoDB dependency
             <exclusions>
                 <exclusion>
                     <groupId>software.amazon.awssdk</groupId>
                     <artifactId>netty-nio-client</artifactId>
                 </exclusion>
                 <exclusion>
                     <groupId>software.amazon.awssdk</groupId>
                     <artifactId>apache-client</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
 
         <dependency>
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>sso</artifactId> <-------- Required for identity center authentication.
         </dependency>
 
         <dependency>
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>ssooidc</artifactId> <-------- Required for identity center authentication.
         </dependency>
 
         <dependency>
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>apache-client</artifactId> <-------- HTTP client specified.
             <exclusions>
                 <exclusion>
                     <groupId>commons-logging</groupId>
                     <artifactId>commons-logging</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>${slf4j.version}</version>
         </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <version>${slf4j.version}</version>
         </dependency>
 
         <!-- Needed to adapt Apache Commons Logging used by Apache HTTP Client to Slf4j to avoid
         ClassNotFoundException: org.apache.commons.logging.impl.LogFactoryImpl during runtime -->
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>jcl-over-slf4j</artifactId>
             <version>${slf4j.version}</version>
         </dependency>
 
         <!-- Test Dependencies -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
             <version>${junit5.version}</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
 
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>${maven.compiler.plugin.version}</version>
             </plugin>
         </plugins>
     </build>
 
 </project>
```

### 3단계: 코드 작성
<a name="GetStartedJavaCode"></a>

다음 코드는 Maven이 생성한 `App` 클래스를 보여줍니다. `main` 메서드는 `Handler` 클래스의 인스턴스를 만든 다음 해당 `sendRequest` 메서드를 호출하는 애플리케이션의 진입점입니다.

#### `App` 클래스
<a name="projectsetup-collapse2"></a>

```
package org.example;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class App {
     private static final Logger logger = LoggerFactory.getLogger(App.class);
 
     public static void main(String... args) {
         logger.info("Application starts");
 
         Handler handler = new Handler();
         handler.sendRequest();
 
         logger.info("Application ends");
     }
 }
```

Maven에서 만든 `DependencyFactory` 클래스에는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html) 인스턴스를 빌드하고 반환하는 `dynamoDbClient` 팩토리 메서드가 포함되어 있습니다. `DynamoDbClient` 인스턴스는 Apache 기반 HTTP 클라이언트의 인스턴스를 사용합니다. 이는 Maven에서 사용할 HTTP 클라이언트를 묻는 메시지가 표시될 때 사용자가 `apache-client`를 지정했기 때문입니다.

다음 코드는 `DependencyFactory` 클래스를 보여줍니다.

#### DependencyFactory 클래스
<a name="code-collapse2"></a>

```
package org.example;
 
 import software.amazon.awssdk.http.apache.ApacheHttpClient;
 import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
 
 /**
  * The module containing all dependencies required by the {@link Handler}.
  */
 public class DependencyFactory {
 
     private DependencyFactory() {}
 
     /**
      * @return an instance of DynamoDbClient
      */
     public static DynamoDbClient dynamoDbClient() {
         return DynamoDbClient.builder()
                        .httpClientBuilder(ApacheHttpClient.builder())
                        .build();
     }
 }
```

`Handler` 클래스에는 프로그램의 기본 로직이 들어 있습니다. `App` 클래스에서 `Handler` 인스턴스가 생성되면 `DependencyFactory`는 `DynamoDbClient` 서비스 클라이언트를 제공합니다. 코드는 `DynamoDbClient` 인스턴스를 사용하여 DynamoDB를 직접 호출합니다.

Maven은 `TODO` 주석과 함께 다음과 같은 `Handler` 클래스를 생성합니다. 자습서의 다음 단계에서는 *`TODO`* 주석을 코드로 대체합니다.

#### Maven에서 생성한 `Handler` 클래스
<a name="code-collapsible3"></a>

```
package org.example;
 
 import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
 
 
 public class Handler {
     private final DynamoDbClient dynamoDbClient;
 
     public Handler() {
         dynamoDbClient = DependencyFactory.dynamoDbClient();
     }
 
     public void sendRequest() {
         // TODO: invoking the API calls using dynamoDbClient.
     }
 }
```

로직을 채우려면 `Handler` 클래스의 전체 내용을 다음 코드로 바꾸세요. `sendRequest` 메서드가 채워지고 필요한 임포트가 추가됩니다.

#### `Handler` 클래스 구현됨
<a name="code-collapse4"></a>

다음 코드는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html) 인스턴스를 사용하여 기존 테이블 목록을 검색합니다. 지정된 계정 및 AWS 리전에 대한 테이블이 있는 경우 코드는 `Logger` 인스턴스를 사용하여 이러한 테이블의 이름을 로깅합니다.

```
package org.example;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
 import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse;
 
 
 public class Handler {
     private final DynamoDbClient dynamoDbClient;
 
     public Handler() {
         dynamoDbClient = DependencyFactory.dynamoDbClient();
     }
 
     public void sendRequest() {
         Logger logger = LoggerFactory.getLogger(Handler.class);
 
         logger.info("calling the DynamoDB API to get a list of existing tables");
         ListTablesResponse response = dynamoDbClient.listTables();
 
         if (!response.hasTableNames()) {
             logger.info("No existing tables found for the configured account & region");
         } else {
             response.tableNames().forEach(tableName -> logger.info("Table: " + tableName));
         }
     }
 }
```

### 4단계: 애플리케이션 빌드 및 실행
<a name="GetStartedRunJava"></a>

프로젝트가 생성되고 전체 `Handler` 클래스가 포함된 후 애플리케이션을 빌드하고 실행합니다.

1. AWS IAM Identity Center 세션이 활성화되어 있는지 확인합니다. 확인하려면 AWS Command Line Interface(AWS CLI) 명령 `aws sts get-caller-identity`를 실행하고 응답을 점검하세요. 활성 세션이 없는 경우 [AWS CLI를 사용하여 로그인](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/setup.html#setup-login-sso)에서 지침을 확인하세요.

1. 터미널 또는 명령 프롬프트 창을 열고 프로젝트 디렉토리 `getstarted`로 이동합니다.

1. 프로젝트를 빌드하려면 다음 명령을 실행합니다.

   ```
   mvn clean package
   ```

1. 애플리케이션을 실행하려면 다음 명령을 사용합니다.

   ```
   mvn exec:java -Dexec.mainClass="org.example.App"
   ```

파일을 확인한 후 객체를 삭제한 다음 버킷을 삭제합니다.

#### Success
<a name="GetStartedSuccessJava"></a>

Maven 프로젝트가 오류 없이 빌드되고 실행되었다면 축하합니다. SDK for Java 2.x를 사용한 첫 Java 애플리케이션 빌드에 성공했습니다.

#### 정리
<a name="GetStartedCleanupJava"></a>

이 자습서를 진행하는 동안 만든 리소스를 정리하려면 `getstarted` 프로젝트 폴더를 삭제합니다.

## AWS SDK for Java 2.x 문서 검토
<a name="ProgrammingWithJavaUseDoc"></a>

[AWS SDK for Java 2.x 개발자 안내서](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/home.html)에서는 전 AWS 서비스에 걸쳐 SDK의 모든 측면을 전체적으로 다룹니다. 다음 주제를 검토하는 것이 좋습니다.
+ [Migrate from version 1.x to 2.x](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/migration.html) - 1.x와 2.x의 차이점에 대한 자세한 설명이 포함되어 있습니다. 이 주제에는 두 주요 버전을 나란히 사용하는 방법에 대한 지침도 포함되어 있습니다.
+ [DynamoDB guide for Java 2.x SDK](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/examples-dynamodb.html) - 테이블 생성, 항목 조작, 항목 검색 등 기본적인 DynamoDB 작업을 수행하는 방법을 보여줍니다. 이 예에서는 하위 수준 인터페이스를 사용합니다. Java에는 [지원되는 인터페이스](#JavaInterfaces) 섹션에 설명된 대로 여러 인터페이스가 있습니다.

**작은 정보**  
이러한 주제를 검토한 후 [AWS SDK for Java 2.x API 참조](https://sdk.amazonaws.com/java/api/latest/)를 북마크하세요. 모든 AWS 서비스를 다루며 기본 API 참조로 사용하는 것이 좋습니다.

## 지원되는 인터페이스
<a name="JavaInterfaces"></a>

AWS SDK for Java 2.x는 원하는 추상화 수준에 따라 다음 인터페이스를 지원합니다.

**Topics**
+ [하위 수준 인터페이스](#LowLevelInterface)
+ [상위 수준 인터페이스](#HighLevelInterface)
+ [문서 인터페이스](#DocumentInterface)
+ [`Query` 예제와 인터페이스 비교](#CompareJavaInterfacesQueryEx)

### 하위 수준 인터페이스
<a name="LowLevelInterface"></a>

하위 수준 인터페이스는 기본 서비스 API에 대한 일대일 매핑을 제공합니다. 이 인터페이스를 통해 모든 DynamoDB API를 사용할 수 있습니다. 즉, 하위 수준 인터페이스가 완전한 기능을 제공할 수 있지만 사용하기가 더 복잡한 경우가 많습니다. 예를 들어, `.s()` 함수를 사용하여 문자열을 저장하고 `.n()` 함수를 사용하여 숫자를 저장합니다. 다음 [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 예시는 하위 수준 인터페이스를 사용하여 항목을 삽입합니다.

```
import org.slf4j.*;
import software.amazon.awssdk.http.crt.AwsCrtHttpClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;

import java.util.Map;

public class PutItem {

    // Create a DynamoDB client with the default settings connected to the DynamoDB
    // endpoint in the default region based on the default credentials provider chain.
    private static final DynamoDbClient DYNAMODB_CLIENT = DynamoDbClient.create();
    private static final Logger LOGGER = LoggerFactory.getLogger(PutItem.class);

    private void putItem() {
        PutItemResponse response = DYNAMODB_CLIENT.putItem(PutItemRequest.builder()
                .item(Map.of(
                        "pk", AttributeValue.builder().s("123").build(),
                        "sk", AttributeValue.builder().s("cart#123").build(),
                        "item_data", AttributeValue.builder().s("YourItemData").build(),
                        "inventory", AttributeValue.builder().n("500").build()
                        // ... more attributes ...
                ))
                .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)
                .tableName("YourTableName")
                .build());
        LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)");
    }
}
```

### 상위 수준 인터페이스
<a name="HighLevelInterface"></a>

AWS SDK for Java 2.x의 상위 수준 인터페이스를 DynamoDB 향상된 클라이언트라고 합니다. 이 인터페이스는 보다 관용적인 코드 작성 경험을 제공합니다.

향상된 클라이언트는 클라이언트 측 데이터 클래스와 해당 데이터를 저장하도록 설계된 DynamoDB 테이블 간에 매핑하는 방법을 제공합니다. 코드에서 테이블과 해당 모델 클래스 간의 관계를 정의합니다. 그러면 SDK를 사용하여 데이터 유형 조작을 관리할 수 있습니다. 향상된 클라이언트에 대한 자세한 내용은 **AWS SDK for Java 2.x 개발자 안내서의 [DynamoDB enhanced client API](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/dynamodb-enhanced-client.html)를 참조하세요.

다음 [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 예시에서는 상위 수준 인터페이스를 사용합니다. 이 예제에서는 `YourItem`이라는 `DynamoDbBean`이 `TableSchema`를 만들어 `putItem()` 직접 호출의 입력으로 바로 사용할 수 있도록 합니다.

```
import org.slf4j.*;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*;
import software.amazon.awssdk.enhanced.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity;

public class DynamoDbEnhancedClientPutItem {
    private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build();
    private static final DynamoDbTable<YourItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromBean(YourItem.class));
    private static final Logger LOGGER = LoggerFactory.getLogger(PutItem.class);

    private void putItem() {
        PutItemEnhancedResponse<YourItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourItem.class)
                .item(new YourItem("123", "cart#123", "YourItemData", 500))
                .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)
                .build());
        LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)");
    }

    @DynamoDbBean
    public static class YourItem {

        public YourItem() {}

        public YourItem(String pk, String sk, String itemData, int inventory) {
            this.pk = pk;
            this.sk = sk;
            this.itemData = itemData;
            this.inventory = inventory;
        }

        private String pk;
        private String sk;
        private String itemData;

        private int inventory;

        @DynamoDbPartitionKey
        public void setPk(String pk) {
            this.pk = pk;
        }

        public String getPk() {
            return pk;
        }

        @DynamoDbSortKey
        public void setSk(String sk) {
            this.sk = sk;
        }

        public String getSk() {
            return sk;
        }

        public void setItemData(String itemData) {
            this.itemData = itemData;
        }

        public String getItemData() {
            return itemData;
        }

        public void setInventory(int inventory) {
            this.inventory = inventory;
        }

        public int getInventory() {
            return inventory;
        }
    }
}
```

AWS SDK for Java 1.x에는 자체 상위 수준 인터페이스가 있으며, 이 인터페이스는 주로 기본 클래스 `DynamoDBMapper`에서 참조합니다. AWS SDK for Java 2.x는 `software.amazon.awssdk.enhanced.dynamodb`라는 별도의 패키지(및 Maven 아티팩트)에 게시됩니다. Java 2.x SDK는 주로 기본 클래스 `DynamoDbEnhancedClient`에서 참조합니다.

#### 변경할 수 없는 데이터 클래스를 사용하는 상위 수준 인터페이스
<a name="HighLevelInterfaceImmutableDataClasses"></a>

DynamoDB 향상된 클라이언트 API의 매핑 기능은 변경 불가능한 데이터 클래스와도 함께 작동합니다. 불변 클래스에는 접근자만 포함되며 SDK가 클래스의 인스턴스를 생성하는 데 사용하는 빌더 클래스가 필요합니다. Java의 불변성은 개발자가 부작용이 없는 클래스를 만드는 데 사용할 수 있는 일반적으로 사용되는 스타일입니다. 복잡한 멀티스레드 애플리케이션에서는 이러한 클래스의 동작을 더 잘 예측할 수 있습니다. 변경 불가능한 클래스는 [High-level interface example](#highleveleg)에 나온 대로 `@DynamoDbBean` 주석을 사용하는 대신 빌더 클래스를 입력으로 사용하는 `@DynamoDbImmutable` 주석을 사용합니다.

다음 예시에서는 빌더 클래스 `DynamoDbEnhancedClientImmutablePutItem`을 입력으로 사용하여 테이블 스키마를 생성합니다. 그런 다음 예시는 [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) API 직접 호출을 위한 입력으로 해당 스키마를 제공합니다.

```
import org.slf4j.*;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.enhanced.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity;

public class DynamoDbEnhancedClientImmutablePutItem {
    private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build();
    private static final DynamoDbTable<YourImmutableItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromImmutableClass(YourImmutableItem.class));
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientImmutablePutItem.class);

    private void putItem() {
        PutItemEnhancedResponse<YourImmutableItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourImmutableItem.class)
                .item(YourImmutableItem.builder()
                                        .pk("123")
                                        .sk("cart#123")
                                        .itemData("YourItemData")
                                        .inventory(500)
                                        .build())
                .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)
                .build());
        LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)");
    }
}
```

다음 예시에서는 변경 불가능한 데이터 클래스를 보여줍니다.

```
@DynamoDbImmutable(builder = YourImmutableItem.YourImmutableItemBuilder.class)
class YourImmutableItem {
    private final String pk;
    private final String sk;
    private final String itemData;
    private final int inventory;
    public YourImmutableItem(YourImmutableItemBuilder builder) {
        this.pk = builder.pk;
        this.sk = builder.sk;
        this.itemData = builder.itemData;
        this.inventory = builder.inventory;
    }

    public static YourImmutableItemBuilder builder() { return new YourImmutableItemBuilder(); }

    @DynamoDbPartitionKey
    public String getPk() {
        return pk;
    }

    @DynamoDbSortKey
    public String getSk() {
        return sk;
    }

    public String getItemData() {
        return itemData;
    }

    public int getInventory() {
        return inventory;
    }

    static final class YourImmutableItemBuilder {
        private String pk;
        private String sk;
        private String itemData;
        private int inventory;

        private YourImmutableItemBuilder() {}

        public YourImmutableItemBuilder pk(String pk) { this.pk = pk; return this; }
        public YourImmutableItemBuilder sk(String sk) { this.sk = sk; return this; }
        public YourImmutableItemBuilder itemData(String itemData) { this.itemData = itemData; return this; }
        public YourImmutableItemBuilder inventory(int inventory) { this.inventory = inventory; return this; }

        public YourImmutableItem build() { return new YourImmutableItem(this); }
    }
}
```

#### 변경 불가능한 데이터 클래스와 서드 파티 보일러플레이트 생성 라이브러리를 사용하는 상위 수준 인터페이스
<a name="ImmutableDataClassesThirdPartyBoilerplateGenLib"></a>

이전 예제에서 언급한 변경 불가능한 데이터 클래스에는 몇 가지 보일러플레이트 코드가 필요합니다. 예를 들어, `Builder` 클래스 외에 데이터 클래스의 게터 및 세터 로직이 필요합니다. [Project Lombok](https://projectlombok.org/)과 같은 서드 파티 라이브러리는 이러한 유형의 보일러플레이트 코드를 생성하는 데 도움이 될 수 있습니다. 대부분의 보일러플레이트 코드를 줄이면 변경 불가능한 데이터 클래스와 AWS SDK를 사용하는 데 필요한 코드의 양을 제한할 수 있습니다. 이를 통해 코드의 생산성과 가독성이 더욱 향상됩니다. **AWS SDK for Java 2.x 자세한 내용은 개발자 안내서의 [Lombok과 같은 타사 라이브러리를 사용](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-use-immut.html#ddb-en-client-use-immut-lombok)을 참조하세요.

다음 예시는 Project Lombok이 DynamoDB 향상된 클라이언트 API를 사용하는 데 필요한 코드를 간소화하는 방법을 보여줍니다.

```
import org.slf4j.*;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.enhanced.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity;

public class DynamoDbEnhancedClientImmutableLombokPutItem {

    private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build();
    private static final DynamoDbTable<YourImmutableLombokItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromImmutableClass(YourImmutableLombokItem.class));
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientImmutableLombokPutItem.class);

    private void putItem() {
        PutItemEnhancedResponse<YourImmutableLombokItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourImmutableLombokItem.class)
                .item(YourImmutableLombokItem.builder()
                        .pk("123")
                        .sk("cart#123")
                        .itemData("YourItemData")
                        .inventory(500)
                        .build())
                .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)
                .build());
        LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)");
    }
}
```

다음 예시에서는 변경 불가능한 데이터 클래스의 변경 불가능한 데이터 객체를 보여줍니다.

```
import lombok.*;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*;

@Builder
@DynamoDbImmutable(builder = YourImmutableLombokItem.YourImmutableLombokItemBuilder.class)
@Value
public class YourImmutableLombokItem {

    @Getter(onMethod_=@DynamoDbPartitionKey)
    String pk;
    @Getter(onMethod_=@DynamoDbSortKey)
    String sk;
    String itemData;
    int inventory;
}
```

`YourImmutableLombokItem` 클래스는 Project Lombok 및 AWS SDK에서 제공하는 다음과 같은 주석을 사용합니다.
+ [@Builder](https://projectlombok.org/features/Builder) - Project Lombok에서 제공하는 데이터 클래스를 위한 복잡한 빌더 API를 생성합니다.
+ [@DynamoDbImmutable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbImmutable.html) - `DynamoDbImmutable` 클래스를 AWS SDK에서 제공하는 DynamoDB 매핑 가능한 엔터티 주석으로 식별합니다.
+ [@Value](https://projectlombok.org/features/Value) - `@Data`의 변경할 수 없는 변형입니다. 기본적으로 모든 필드는 비공개 및 최종본으로 설정되고 세터가 생성되지 않습니다. Project Lombok은 이 주석을 제공합니다.

### 문서 인터페이스
<a name="DocumentInterface"></a>

AWS SDK for Java 2.x 문서 인터페이스를 사용하면 데이터 유형 설명자를 지정할 필요가 없습니다. 데이터 형식은 데이터 자체의 의미론으로 암시됩니다. 이 문서 인터페이스는 AWS SDK for Java 1.x, 문서 인터페이스와 비슷하지만, 인터페이스가 새롭게 디자인되었습니다.

다음 [Document interface example](#DocInterfaceEg)는 Document 인터페이스를 사용하여 표현된 `PutItem` 직접 호출을 보여줍니다. 이 예시에서는 EnhancedDocument도 사용합니다. 향상된 문서 API를 사용하여 DynamoDB 테이블에 대해 명령을 실행하려면 먼저 테이블을 문서 테이블 스키마와 연결하여 `DynamoDBTable` 리소스 개체를 생성해야 합니다. 문서 테이블 스키마 빌더에는 기본 인덱스 키와 속성 변환기 제공자가 필요합니다.

`AttributeConverterProvider.defaultProvider()`를 사용하여 기본 유형의 문서 속성을 변환할 수 있습니다. 사용자 지정 `AttributeConverterProvider` 구현으로 전체 기본 동작을 변경할 수 있습니다. 단일 속성의 변환기를 변경할 수도 있습니다. [AWS SDK 및 도구 참조 안내서](https://docs.aws.amazon.com/sdkref/latest/guide/version-support-matrix.html)에는 사용자 지정 변환기를 사용하는 방법에 대한 자세한 내용과 예제가 나와 있습니다. 기본적인 용도는 기본 변환기를 사용할 수 없는 도메인 클래스의 속성을 위한 것입니다. 사용자 지정 변환기를 사용하면 DynamoDB에 쓰거나 읽는 데 필요한 정보를 SDK에 제공할 수 있습니다.

```
import org.slf4j.*;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.enhanced.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity;

public class DynamoDbEnhancedDocumentClientPutItem {
    private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build();
    private static final DynamoDbTable<EnhancedDocument> DYNAMODB_TABLE =
            ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.documentSchemaBuilder()
                            .addIndexPartitionKey(TableMetadata.primaryIndexName(),"pk", AttributeValueType.S)
                            .addIndexSortKey(TableMetadata.primaryIndexName(), "sk", AttributeValueType.S)
                            .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                            .build());

    private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedDocumentClientPutItem.class);

    private void putItem() {
        PutItemEnhancedResponse<EnhancedDocument> response = DYNAMODB_TABLE.putItemWithResponse(
                        PutItemEnhancedRequest.builder(EnhancedDocument.class)
                                .item(
                                    EnhancedDocument.builder()
                                            .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                                            .putString("pk", "123")
                                            .putString("sk", "cart#123")
                                            .putString("item_data", "YourItemData")
                                            .putNumber("inventory", 500)
                                            .build())
                                .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)
                                .build());
        LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)");
    }

}
```

다음 유틸리티 메서드를 사용하여 JSON 문서를 기본 Amazon DynamoDB 데이터 유형으로, 또 그 반대로 변환할 수 있습니다.
+ [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html#fromJson(java.lang.String)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html#fromJson(java.lang.String)) – JSON 문자열에서 새로운 EnhancedDocument 인스턴스를 생성합니다.
+ [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html#toJson()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html#toJson()) – 다른 JSON 개체처럼 애플리케이션에서 사용할 수 있도록 문서의 JSON 문자열 표현을 생성합니다.

### `Query` 예제와 인터페이스 비교
<a name="CompareJavaInterfacesQueryEx"></a>

이 섹션에서는 다양한 인터페이스를 사용하여 동일한 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) 직접 호출을 표현한 것을 보여줍니다. 이러한 쿼리의 결과를 세밀하게 조정하려면 다음 사항에 유의하세요.
+ DynamoDB는 하나의 특정 파티션 키 값을 대상으로 하므로, 파티션 키를 완전히 지정해야 합니다.
+ 정렬 키에는 카트 항목만 이 쿼리의 대상으로 지정되도록 `begins_with`를 사용하는 키 조건 표현식이 있습니다.
+ 쿼리를 최대 100개의 반환 항목으로 제한하는 데 `limit()`을 사용합니다.
+ `scanIndexForward`를 false로 설정합니다. 결과는 UTF-8 바이트 순으로 반환되며, 이는 일반적으로 숫자가 가장 작은 카트 항목이 먼저 반환됨을 의미합니다. `scanIndexForward`를 false로 설정하면 순서가 반대가 되고 숫자가 가장 큰 카트 항목이 먼저 반환됩니다.
+ 필터를 적용하여 기준과 일치하지 않는 모든 결과를 제거합니다. 필터링되는 데이터는 항목이 필터와 일치하는지와 관계없이 읽기 용량을 소비합니다.

**Example 하위 수준 인터페이스를 사용한 `Query`**  
다음 예제에서는 `keyConditionExpression`를 사용하여 이름이 `YourTableName`인 테이블을 쿼리합니다. 이를 통해 쿼리를 특정 파티션 키 값 및 특정 접두사 값으로 시작하는 정렬 키 값으로 제한합니다. 이러한 키 조건은 DynamoDB에서 읽는 데이터의 양을 제한합니다. 마지막으로 쿼리는 `filterExpression`을 사용하여 DynamoDB에서 검색한 데이터에 필터를 적용합니다.  

```
import org.slf4j.*;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;

import java.util.Map;

public class Query {

    // Create a DynamoDB client with the default settings connected to the DynamoDB 
    // endpoint in the default region based on the default credentials provider chain.
    private static final DynamoDbClient DYNAMODB_CLIENT = DynamoDbClient.builder().build();
    private static final Logger LOGGER = LoggerFactory.getLogger(Query.class);

    private static void query() {
        QueryResponse response = DYNAMODB_CLIENT.query(QueryRequest.builder()
                .expressionAttributeNames(Map.of("#name", "name"))
                .expressionAttributeValues(Map.of(
                    ":pk_val", AttributeValue.fromS("id#1"),
                    ":sk_val", AttributeValue.fromS("cart#"),
                    ":name_val", AttributeValue.fromS("SomeName")))
                .filterExpression("#name = :name_val")
                .keyConditionExpression("pk = :pk_val AND begins_with(sk, :sk_val)")
                .limit(100)
                .scanIndexForward(false)
                .tableName("YourTableName")
                .build());

        LOGGER.info("nr of items: " + response.count());
        LOGGER.info("First item pk: " + response.items().get(0).get("pk"));
        LOGGER.info("First item sk: " + response.items().get(0).get("sk"));
    }
}
```

**Example 문서 인터페이스를 사용한 `Query`**  
다음 예제에서는 문서 인터페이스를 사용하여 이름이 `YourTableName`인 테이블을 쿼리합니다.  

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.enhanced.dynamodb.model.*;

import java.util.Map;

public class DynamoDbEnhancedDocumentClientQuery {

    // Create a DynamoDB client with the default settings connected to the DynamoDB 
    // endpoint in the default region based on the default credentials provider chain.
    private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build();
    private static final DynamoDbTable<EnhancedDocument> DYNAMODB_TABLE =
            ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.documentSchemaBuilder()
                    .addIndexPartitionKey(TableMetadata.primaryIndexName(),"pk", AttributeValueType.S)
                    .addIndexSortKey(TableMetadata.primaryIndexName(), "sk", AttributeValueType.S)
                    .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                    .build());
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedDocumentClientQuery.class);

    private void query() {
        PageIterable<EnhancedDocument> response = DYNAMODB_TABLE.query(QueryEnhancedRequest.builder()
                .filterExpression(Expression.builder()
                        .expression("#name = :name_val")
                        .expressionNames(Map.of("#name", "name"))
                        .expressionValues(Map.of(":name_val", AttributeValue.fromS("SomeName")))
                        .build())
                .limit(100)
                .queryConditional(QueryConditional.sortBeginsWith(Key.builder()
                        .partitionValue("id#1")
                        .sortValue("cart#")
                        .build()))
                .scanIndexForward(false)
                .build());

        LOGGER.info("nr of items: " + response.items().stream().count());
        LOGGER.info("First item pk: " + response.items().iterator().next().getString("pk"));
        LOGGER.info("First item sk: " + response.items().iterator().next().getString("sk"));

    }
}
```

**Example 상위 수준 인터페이스를 사용한 `Query`**  
다음 예시는 DynamoDB 향상된 클라이언트 API를 사용하여 이름이 `YourTableName`인 테이블을 쿼리합니다.  

```
import org.slf4j.*;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*;
import software.amazon.awssdk.enhanced.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

import java.util.Map;

public class DynamoDbEnhancedClientQuery {

    private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build();
    private static final DynamoDbTable<YourItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromBean(DynamoDbEnhancedClientQuery.YourItem.class));
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientQuery.class);

    private void query() {
        PageIterable<YourItem> response = DYNAMODB_TABLE.query(QueryEnhancedRequest.builder()
                .filterExpression(Expression.builder()
                        .expression("#name = :name_val")
                        .expressionNames(Map.of("#name", "name"))
                        .expressionValues(Map.of(":name_val", AttributeValue.fromS("SomeName")))
                        .build())
                .limit(100)
                .queryConditional(QueryConditional.sortBeginsWith(Key.builder()
                        .partitionValue("id#1")
                        .sortValue("cart#")
                        .build()))
                .scanIndexForward(false)
                .build());

        LOGGER.info("nr of items: " + response.items().stream().count());
        LOGGER.info("First item pk: " + response.items().iterator().next().getPk());
        LOGGER.info("First item sk: " + response.items().iterator().next().getSk());
    }

    @DynamoDbBean
    public static class YourItem {

        public YourItem() {}

        public YourItem(String pk, String sk, String name) {
            this.pk = pk;
            this.sk = sk;
            this.name = name;
        }

        private String pk;
        private String sk;
        private String name;

        @DynamoDbPartitionKey
        public void setPk(String pk) {
            this.pk = pk;
        }

        public String getPk() {
            return pk;
        }

        @DynamoDbSortKey
        public void setSk(String sk) {
            this.sk = sk;
        }

        public String getSk() {
            return sk;
        }

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

        public String getName() {
            return name;
        }
    }
}
```
**변경할 수 없는 데이터 클래스를 사용하는 상위 수준 인터페이스**  
상위 수준의 변경 불가능한 데이터 클래스를 사용하여 `Query`를 수행하는 경우 엔터티 클래스 `YourItem` 또는 `YourImmutableItem`의 구성을 제외하면 코드는 상위 수준 인터페이스 예제와 동일합니다. 자세한 내용은 [PutItem](#HighLevelImmutableDataClassEg) 예시를 참조하세요.
**변경 불가능한 데이터 클래스와 서드 파티 보일러플레이트 생성 라이브러리를 사용하는 상위 수준 인터페이스**  
상위 수준의 변경 불가능한 데이터 클래스를 사용하여 `Query`를 수행하는 경우 엔터티 클래스 `YourItem` 또는 `YourImmutableLombokItem`의 구성을 제외하면 코드는 상위 수준 인터페이스 예제와 동일합니다. 자세한 내용은 [PutItem](#HighLevelImmutableDataClassEg) 예시를 참조하세요.

## 추가 코드 예시
<a name="AdditionalCodeEx"></a>

SDK for Java 2.x와 함께 DynamoDB를 사용하는 방법에 대한 추가 예제는 다음 코드 예제 리포지토리를 참조하세요.
+ [공식 AWS 단일 작업 코드 예시](https://docs.aws.amazon.com/code-library/latest/ug/java_2_dynamodb_code_examples.html)
+ [커뮤니티에서 유지 관리하는 단일 작업 코드 예시](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/examples/SDK/java)
+ [공식 AWS 시나리오 지향 코드 예시](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/examples/SDK/java)

## 동기식 및 비동기식 프로그래밍
<a name="SyncAsyncProgramming"></a>

AWS SDK for Java 2.x는 DynamoDB와 같은 AWS 서비스에 ****동기식 클라이언트와 비동기식 클라이언트를 모두 제공합니다.

`DynamoDbClient` 및 `DynamoDbEnhancedClient` 클래스는 클라이언트가 서비스로부터 응답을 받을 때까지 스레드의 실행을 차단하는 동기식 메서드를 제공합니다. 이 클라이언트는 비동기식 작업이 필요 없는 경우 DynamoDB와 상호 작용하는 가장 간단한 방법입니다.

`DynamoDbAsyncClient` 및 `DynamoDbEnhancedAsyncClient` 클래스는 즉시 반환하는 비동기식 메서드를 제공하며, 응답을 기다리지 않고 제어 권한을 직접 호출하는 스레드에 넘겨줍니다. 비차단 클라이언트는 몇 개의 스레드에서 높은 동시성을 사용하여 최소한의 컴퓨팅 리소스로 I/O 요청을 효율적으로 처리할 수 있다는 이점이 있습니다. 이를 통해 처리량과 응답성이 향상됩니다.

AWS SDK for Java 2.x는 비차단 I/O에 대한 기본 지원을 사용합니다. AWS SDK for Java 1.x는 비차단 I/O를 시뮬레이션해야 했습니다.

동기식 메서드는 응답이 제공되기 전에 반환하므로, 준비되었을 때 응답을 가져올 방법이 필요합니다. AWS SDK for Java의 비동기식 메서드는 미래의 비동기식 작업 결과를 포함하는 [https://docs.oracle.com/javase/8/docs/api/index.html?java/util/concurrent/CompletableFuture.html](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/concurrent/CompletableFuture.html) 객체를 반환합니다. 이러한 `CompletableFuture` 개체에서 `get()` 또는 `join()`을 직접 호출하면 결과가 나올 때까지 코드가 차단됩니다. 요청과 동시에 직접 호출을 수행하면 일반 동기식 직접 호출과 동작이 비슷합니다.

비동기 프로그래밍에 대한 자세한 내용은 **AWS SDK for Java 2.x 개발자 안내서의 [Use asynchronous programming](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/asynchronous.html)을 참조하세요.

## HTTP 클라이언트
<a name="HttpClients"></a>

모든 클라이언트를 지원하기 위해 AWS 서비스와의 통신을 처리하는 HTTP 클라이언트가 있습니다. 애플리케이션에 가장 적합한 특성을 가진 클라이언트를 선택하여 대체 HTTP 클라이언트를 연결할 수 있습니다. 어떤 것은 더 가볍고 어떤 것은 더 많은 구성 옵션을 제공합니다.

어떤 HTTP 클라이언트는 동기식 사용만 지원하는 반면 어떤 HTTP 클라이언트는 비동기식 사용만 지원합니다. 워크로드에 적합한 HTTP 클라이언트를 선택하는 데 도움이 되는 흐름도는 **AWS SDK for Java 2.x 개발자 안내서의 [HTTP 클라이언트 권장 사항](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration.html#http-clients-recommend)에서 참조하세요.

다음 목록은 가능한 HTTP 클라이언트 중 일부를 보여줍니다.

**Topics**
+ [Apache 기반 HTTP 클라이언트](#ApacheHttpClient)
+ [`URLConnection` 기반 HTTP 클라이언트](#URLConnHttpClient)
+ [Netty 기반 HTTP 클라이언트](#NettyHttpClient)
+ [AWS CRT 기반 HTTP 클라이언트](#AWSCRTHttpClient)

### Apache 기반 HTTP 클라이언트
<a name="ApacheHttpClient"></a>

이 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.html) 클래스는 동기식 서비스 클라이언트를 지원합니다. 동기식 사용의 기본 HTTP 클라이언트입니다. `ApacheHttpClient` 클래스 구성에 대한 자세한 내용은 **AWS SDK for Java 2.x 개발자 안내서의 [Configure the Apache-based HTTP client](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-apache.html)에서 참조하세요.

### `URLConnection` 기반 HTTP 클라이언트
<a name="URLConnHttpClient"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.html) 클래스는 동기식 클라이언트를 위한 또 다른 옵션입니다. Apache 기반 HTTP 클라이언트보다 로드 속도가 빠르지만 기능이 더 적습니다. `UrlConnectionHttpClient` 클래스 구성에 대한 자세한 내용은 **AWS SDK for Java 2.x 개발자 안내서의 [Configure the URLConnection-based HTTP client](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-url.html)에서 참조하세요.

### Netty 기반 HTTP 클라이언트
<a name="NettyHttpClient"></a>

이 `NettyNioAsyncHttpClient` 클래스는 비동기식 클라이언트를 지원합니다. 비동기식으로 사용 시 기본으로 선택됩니다. `NettyNioAsyncHttpClient` 클래스 구성에 대한 자세한 내용은 **AWS SDK for Java 2.x 개발자 안내서의 [Configure the Netty-based HTTP client](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-netty.html)에서 참조하세요.

### AWS CRT 기반 HTTP 클라이언트
<a name="AWSCRTHttpClient"></a>

AWS Common Runtime(CRT) 라이브러리의 최신 `AwsCrtHttpClient` 및 `AwsCrtAsyncHttpClient` 클래스는 동기식 및 비동기식 클라이언트를 지원하는 더 많은 옵션입니다. 다른 HTTP 클라이언트와 비교하여 AWS CRT는 다음을 제공합니다.
+ 더 빠른 SDK 시작 시간
+ 더 작은 메모리 공간
+ 대기 시간 단축
+ 연결 상태 관리
+ DNS 로드 밸런싱

`AwsCrtHttpClient` 및 `AwsCrtAsyncHttpClient` 클래스 구성에 대한 자세한 내용은 *AWS SDK for Java 2.x* 개발자 안내서의 [Configure the AWS CRT-based HTTP clients](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-crt.html)에서 참조하세요.

AWS CRT 기반 HTTP 클라이언트는 기존 애플리케이션의 하위 호환성을 깨뜨릴 수 있으므로, 기본값이 아닙니다. 하지만 DynamoDB의 경우 동기식 및 비동기식 사용에 AWS CRT 기반 HTTP 클라이언트를 사용하는 것이 좋습니다.

AWS CRT 기반 HTTP 클라이언트에 대한 소개는 *AWS* 개발자 도구 블로그의 [Announcing availability of the AWS CRT HTTP Client in the AWS SDK for Java 2.x](https://aws.amazon.com/blogs/developer/announcing-availability-of-the-aws-crt-http-client-in-the-aws-sdk-for-java-2-x/)에서 참조하세요.

## HTTP 클라이언트 구성
<a name="ConfigHttpClient"></a>

클라이언트를 구성할 때 다음과 같은 다양한 구성 옵션을 제공할 수 있습니다.
+ API 직접 호출의 다양한 측면에 대한 제한 시간 설정.
+ TCP 연결 유지 활성화.
+ 오류 발생 시 재시도 정책 제어.
+ [실행 인터셉터](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/interceptors.html) 인스턴스가 수정할 수 있는 실행 속성 지정. 실행 인터셉터는 API 요청 및 응답의 실행을 가로채는 코드를 작성할 수 있습니다. 이를 통해 지표를 게시하고 진행 중인 요청을 수정하는 등의 작업을 수행할 수 있습니다.
+ HTTP 헤더 추가 또는 조작.
+ [클라이언트 측 성능 지표](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/metrics.html) 추적 활성화. 이 기능을 사용하면 애플리케이션의 서비스 클라이언트에 대한 지표를 수집하고 Amazon CloudWatch에서 출력을 분석할 수 있습니다.
+ 비동기식 재시도 및 제한 시간 작업과 같은 일정 예약 작업에 사용할 대체 실행자 서비스 지정.

서비스 클라이언트 `Builder` 클래스에 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/client/config/ClientOverrideConfiguration.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/client/config/ClientOverrideConfiguration.html) 객체를 제공하여 구성을 제어합니다. 다음 섹션의 일부 코드 예시에서 이를 확인할 수 있습니다.

`ClientOverrideConfiguration`은 표준 구성 선택 사항을 제공합니다. 다양한 플러그 가능 HTTP 클라이언트에는 구현별 구성 기능도 있습니다.

**Topics**
+ [제한 시간 구성](#TimeoutConfig)
+ [RetryMode](#RetryMode)
+ [DefaultsMode](#DefaultsMode)
+ [연결 유지 구성](#KeepAliveConfig)

### 제한 시간 구성
<a name="TimeoutConfig"></a>

클라이언트 구성을 조정하여 서비스 직접 호출과 관련된 다양한 제한 시간을 제어할 수 있습니다. DynamoDB는 다른 AWS 서비스에 비해 지연 시간이 더 짧습니다. 따라서 네트워킹 문제가 발생할 경우 빠르게 실패할 수 있도록 이러한 속성을 조정하여 제한 시간 값을 낮추는 것이 좋습니다.

DynamoDB 클라이언트에서 `ClientOverrideConfiguration`을 사용하거나 기본 HTTP 클라이언트 구현에서 세부 구성 옵션을 변경하여 지연 시간 관련 동작을 사용자 지정할 수 있습니다.

`ClientOverrideConfiguration`을 사용하여 다음과 같은 영향력 있는 속성을 구성할 수 있습니다.
+ `apiCallAttemptTimeout` – 포기하고 제한 시간이 초과되기 전에 한 번의 HTTP 요청 시도가 완료되기까지 기다리는 시간입니다.
+ `apiCallTimeout` – 클라이언트가 API 직접 호출을 완전히 실행해야 하는 시간입니다. 여기에는 재시도를 포함한 모든 HTTP 요청으로 구성된 요청 핸들러 실행이 포함됩니다.

AWS SDK for Java 2.x는 연결 제한 시간 및 소켓 제한 시간 등의 일부 제한 시간 옵션에 [기본값](https://github.com/aws/aws-sdk-java-v2/blob/a0c8a0af1fa572b16b5bd78f310594d642324156/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java#L134)을 제공합니다. SDK는 API 직접 호출 제한 시간 또는 개별 API 직접 호출 시도 제한 시간에 기본값을 제공하지 않습니다. 이러한 제한 시간이 `ClientOverrideConfiguration`에 설정되지 않은 경우 SDK는 전체 API 직접 호출 제한 시간 대신 소켓 제한 시간 값을 사용합니다. 소켓 제한 시간의 기본값은 30초입니다.

### RetryMode
<a name="RetryMode"></a>

제한 시간 구성과 관련하여 고려해야 하는 또 다른 구성은 `RetryMode` 구성 객체입니다. 이 구성 객체에는 재시도 동작 모음이 포함되어 있습니다.

SDK for Java 2.x는 다음 재시도 모드를 지원합니다.
+ `legacy` – 명시적으로 변경하지 않는 경우 기본 재시도 모드입니다. 이 재시도 모드는 Java SDK에만 해당됩니다. 최대 3회 재시도 또는 DynamoDB와 같은 서비스의 경우 최대 8회 재시도할 수 있습니다.
+ `standard` - 다른 AWS SDK와 더 일관적이기 때문에 '표준'이라는 이름이 지정되었습니다. 이 모드는 첫 번째 재시도를 위해 0ms에서 1,000ms 사이의 임의의 시간 동안 기다립니다. 다시 재시도해야 하는 경우 이 모드는 0ms에서 1,000ms 사이의 또 다른 임의의 시간을 선택하여 2를 곱합니다. 추가 재시도가 필요한 경우 같은 범위에서 임의로 선택한 시간에 4를 곱하는 식으로 반복합니다. 각 대기 시간은 20초로 제한됩니다. 이 모드는 `legacy` 모드보다 더 많이 감지된 장애 조건에 대해 재시도를 수행합니다. DynamoDB의 경우 [numRetries](#numRetries)로 재정의하지 않는 한 모두 합쳐 최대 3회까지 시도합니다.
+ `adaptive` – `standard` 모드를 기반으로 하며 AWS 요청 비율을 동적으로 제한하여 성공률을 극대화합니다. 이렇게 하면 요청 지연 시간이 길어질 수 있습니다. 예측 가능한 지연 시간이 중요한 경우에는 적응형 재시도 모드를 사용하지 않는 것이 좋습니다.

이러한 재시도 모드의 확장된 정의는 **AWS SDK 및 도구 참조 안내서의 [Retry behavior](https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html)에서 확인할 수 있습니다.

#### 재시도 정책
<a name="RetryPolicies"></a>

모든 `RetryMode` 구성에는 하나 이상의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/conditions/RetryCondition.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/conditions/RetryCondition.html) 구성을 기반으로 구축된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/RetryPolicy.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/RetryPolicy.html)가 있습니다. [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/conditions/TokenBucketRetryCondition.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/conditions/TokenBucketRetryCondition.html)은 DynamoDB SDK 클라이언트 구현의 재시도 동작에 특히 중요합니다. 이 조건은 토큰 버킷 알고리즘을 사용하여 SDK의 재시도 횟수를 제한합니다. 선택한 재시도 모드에 따라 제한 예외가 `TokenBucket`에서 토큰을 뺄 수도 있고 빼지 않을 수도 있습니다.

클라이언트에서 제한 예외 또는 일시적 서버 오류와 같은 재시도 가능한 오류가 발생하면 SDK는 요청을 자동으로 재시도합니다. 재시도 횟수와 속도를 제어할 수 있습니다.

클라이언트를 구성할 때 다음 파라미터를 지원하는 `RetryPolicy`를 제공할 수 있습니다.
+ `numRetries` – 요청이 실패한 것으로 간주되기 전에 적용해야 하는 최대 재시도 횟수입니다. 사용하는 재시도 모드와 관계없이 기본값은 8입니다.
**주의**  
이 기본값을 변경하려면 신중하게 고려하시기 바랍니다.
+ `backoffStrategy` - 재시도에 적용할 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/backoff/BackoffStrategy.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/backoff/BackoffStrategy.html)으로, [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/backoff/FullJitterBackoffStrategy.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/backoff/FullJitterBackoffStrategy.html)이 기본 전략입니다. 이 전략은 현재 재시도 횟수, 기본 지연 및 최대 백오프 시간을 기준으로 추가 재시도 사이에 기하급수적 지연을 수행합니다. 그런 다음 지터를 추가하여 약간의 무작위성을 제공합니다. 기하급수적 지연에 사용되는 기본 지연은 재시도 모드와 관계없이 25ms입니다.
+ `retryCondition` – [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/conditions/RetryCondition.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/conditions/RetryCondition.html)은 요청을 재시도할지 여부를 결정합니다. 기본적으로 재시도 가능하다고 판단되는 특정 HTTP 상태 코드 및 예외 집합을 재시도합니다. 대부분의 경우 기본 구성이면 충분합니다.

다음 코드는 대체 재시도 정책을 제공합니다. 총 5번의 재시도(총 6번의 요청)를 지정합니다. 첫 번째 재시도는 약 100ms 지연 후에 이루어져야 하며, 각 추가 재시도는 최대 1초 지연까지 기하급수적으로 2배 증가해야 합니다.

```
DynamoDbClient client = DynamoDbClient.builder()
    .overrideConfiguration(ClientOverrideConfiguration.builder()
        .retryPolicy(RetryPolicy.builder()
            .backoffStrategy(FullJitterBackoffStrategy.builder()
                .baseDelay(Duration.ofMillis(100))
                .maxBackoffTime(Duration.ofSeconds(1))
                .build())
            .numRetries(5)
            .build())
        .build())
    .build();
```

### DefaultsMode
<a name="DefaultsMode"></a>

`ClientOverrideConfiguration` 및 `RetryMode`가 관리하지 않는 제한 시간 속성은 일반적으로 `DefaultsMode`를 지정하여 암묵적으로 구성됩니다.

AWS SDK for Java 2.x(버전 2.17.102 이상)에 `DefaultsMode`에 대한 지원이 도입되었습니다. 이 기능은 HTTP 통신 설정, 재시도 동작, 서비스 리전 엔드포인트 설정 및 잠재적인 모든 SDK 관련 구성과 같은 일반적인 구성 가능 설정에 대한 기본값 집합을 제공합니다. 이 기능을 사용하면 일반 사용 시나리오에 맞게 조정된 새 구성 기본값을 얻을 수 있습니다.

기본 모드는 모든 AWS SDK에서 표준화되어 있습니다. SDK for Java 2.x는 다음과 같은 기본 모드를 지원합니다.
+ `legacy` - AWS SDK에 따라 달라지고 `DefaultsMode`가 설정되기 전에 존재했던 기본 설정을 제공합니다.
+ `standard` - 대부분의 시나리오에 최적화되지 않은 기본 설정을 제공합니다.
+ `in-region` - 표준 모드를 기반으로 구축하며 동일한 AWS 리전에서 AWS 서비스를 직접 호출하는 애플리케이션에 맞게 조정된 설정을 포함합니다.
+ `cross-region` - 표준 모드를 기반으로 구축하며 동일한 다른 리전에서 AWS 서비스를 직접 호출하는 애플리케이션에 제한 시간이 긴 설정을 포함합니다.
+ `mobile` - 표준 모드를 기반으로 구축하며 지연 시간이 긴 모바일 애플리케이션에 맞게 조정된 제한 시간이 긴 설정을 포함합니다.
+ `auto`— 표준 모드를 기반으로 구축하며 실험적 기능을 포함합니다. SDK는 런타임 환경을 검색하여 적절한 설정을 자동으로 결정합니다. 자동 감지는 휴리스틱 기반이며 정확도가 100%는 아닙니다. 런타임 환경을 확인할 수 없는 경우 표준 모드가 사용됩니다. 자동 탐지는 [인스턴스 메타데이터와 사용자 데이터](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html)를 쿼리할 수 있으며, 이로 인해 지연이 발생할 수 있습니다. 시작 지연 시간이 애플리케이션에 중요한 경우에는 명시적 지연 시간을 `DefaultsMode`을 대신 선택하는 것이 좋습니다.

다음과 같은 방법으로 기본 모드를 구성할 수 있습니다.
+ `AwsClientBuilder.Builder#defaultsMode(DefaultsMode)`를 통해 클라이언트에게 직접 전달합니다.
+ `defaults_mode` 프로필 파일 속성을 통해 구성 프로필에서 구성합니다.
+ `aws.defaultsMode` 시스템 속성을 통해 전역적으로 구성합니다.
+ `AWS_DEFAULTS_MODE` 환경 변수를 통해 전역적으로 구성합니다.

**참고**  
`legacy` 외의 모든 모드에서는 모범 사례가 발전함에 따라 벤딩 기본값이 변경될 수 있습니다. 따라서 `legacy` 이외의 모드를 사용하는 경우 SDK를 업그레이드할 때 테스트를 수행하는 것이 좋습니다.

**AWS SDK 및 도구 참조 안내서의 [스마트 구성 기본값](https://docs.aws.amazon.com/sdkref/latest/guide/feature-smart-config-defaults.html)은 다양한 기본 모드의 구성 속성 및 기본값 목록을 제공합니다.

애플리케이션의 특성과 애플리케이션이 상호 작용하는 AWS 서비스에 따라 기본 모드 값을 선택합니다.

이러한 값은 다양한 AWS 서비스 선택지를 염두에 두고 구성되었습니다. DynamoDB 테이블과 애플리케이션이 모두 한 리전에 배포되는 일반적인 DynamoDB 배포의 경우 `standard` 기본 모드 중에서 `in-region` 기본 모드가 가장 적합합니다.

**Example 지연 시간이 짧은 직접 호출을 위해 조정된 DynamoDB SDK 클라이언트 구성**  
다음 예시는 지연 시간이 짧을 것으로 예상되는 DynamoDB 직접 호출의 제한 시간을 더 낮은 값으로 조정합니다.  

```
DynamoDbAsyncClient asyncClient = DynamoDbAsyncClient.builder()
    .defaultsMode(DefaultsMode.IN_REGION)
    .httpClientBuilder(AwsCrtAsyncHttpClient.builder())
    .overrideConfiguration(ClientOverrideConfiguration.builder()
        .apiCallTimeout(Duration.ofSeconds(3))
        .apiCallAttemptTimeout(Duration.ofMillis(500))
        .build())
    .build();
```
개별 HTTP 클라이언트 구현을 통해 제한 시간 및 연결 사용 동작을 훨씬 더 세밀하게 제어할 수 있습니다. 예를 들어, AWS CRT 기반 클라이언트의 경우 클라이언트가 사용된 연결의 상태를 능동적으로 모니터링할 수 있도록 `ConnectionHealthConfiguration`을 활성화할 수 있습니다. 자세한 내용은 **AWS SDK for Java 2.x 개발자 안내서의 [AWS CRT 기반 HTTP 클라이언트의 고급 구성](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-crt.html#configuring-the-crt-based-http-client)을 참조하세요.

### 연결 유지 구성
<a name="KeepAliveConfig"></a>

연결 유지를 활성화하면 연결을 재사용하여 지연 시간을 줄일 수 있습니다. 연결 유지에는 HTTP 연결 유지와 TCP 연결 유지라는 두 가지 종류가 있습니다.
+ HTTP 연결 유지는 클라이언트와 서버 간의 HTTPS 연결을 유지하려고 시도하므로 이후 요청에서 해당 연결을 재사용할 수 있습니다. 이렇게 하면 이후 요청 시 무거운 HTTPS 인증을 건너뛸 수 있습니다. HTTP 연결 유지는 모든 클라이언트에서 기본적으로 활성화되어 있습니다.
+ TCP 연결 유지는 기본 운영 체제에 소켓 연결을 통해 작은 패킷을 전송하도록 요청하여 소켓의 연결이 유지되어 있는지 확인하고 연결 해제가 발생하면 즉시 감지하도록 합니다. 이렇게 하면 나중에 요청할 때 연결이 해제된 소켓을 사용하느라 시간을 허비하지 않아도 됩니다. TCP 연결 유지는 모든 클라이언트에서 기본적으로 비활성화되어 있습니다. 다음 코드 예제에서는 각 HTTP 클라이언트에서 이를 활성화하는 방법을 보여줍니다. CRT 기반이 아닌 모든 HTTP 클라이언트에 대해 활성화된 경우 실제 연결 유지 메커니즘은 운영 체제에 따라 달라집니다. 따라서 운영 체제를 통해 추가 TCP 연결 유지 값(예: 제한 시간 및 패킷 수)을 구성해야 합니다. Linux 또는 macOS의 `sysctl`을 사용하거나 Windows의 레지스트리 값을 사용하여 이 작업을 수행할 수 있습니다.

**Example Apache 기반 HTTP 클라이언트에서 TCP 연결 유지를 활성화하는 방법**  

```
DynamoDbClient client = DynamoDbClient.builder()
    .httpClientBuilder(ApacheHttpClient.builder().tcpKeepAlive(true))
    .build();
```

**`URLConnection` 기반 HTTP 클라이언트**  
`URLConnection` 기반 HTTP 클라이언트 [https://docs.oracle.com/javase/8/docs/api/java/net/HttpURLConnection.html](https://docs.oracle.com/javase/8/docs/api/java/net/HttpURLConnection.html)을 사용하는 동기식 클라이언트에는 연결 유지를 활성화하는 [메커니즘](https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html)이 없습니다.

**Example Netty 기반 HTTP 클라이언트에서 TCP 연결 유지 활성화**  

```
DynamoDbAsyncClient client = DynamoDbAsyncClient.builder()
    .httpClientBuilder(NettyNioAsyncHttpClient.builder().tcpKeepAlive(true))
    .build();
```

**Example AWS CRT 기반 HTTP 클라이언트에서 TCP 연결 유지 활성화**  
AWS CRT 기반 HTTP 클라이언트를 사용하여 TCP 연결 유지를 활성화하고 기간을 제어할 수 있습니다.  

```
DynamoDbClient client = DynamoDbClient.builder()
    .httpClientBuilder(AwsCrtHttpClient.builder()
    .tcpKeepAliveConfiguration(TcpKeepAliveConfiguration.builder()
        .keepAliveInterval(Duration.ofSeconds(50))
        .keepAliveTimeout(Duration.ofSeconds(5))
        .build()))
    .build();
```
비동기식 DynamoDB 클라이언트를 사용하는 경우 다음 코드와 같이 TCP 연결 유지를 활성화할 수 있습니다.  

```
DynamoDbAsyncClient client = DynamoDbAsyncClient.builder()
    .httpClientBuilder(AwsCrtAsyncHttpClient.builder()
    .tcpKeepAliveConfiguration(TcpKeepAliveConfiguration.builder()
        .keepAliveInterval(Duration.ofSeconds(50))
        .keepAliveTimeout(Duration.ofSeconds(5))
        .build()))
    .build();
```

## 오류 처리
<a name="JavaErrorHandling"></a>

예외 처리와 관련하여 AWS SDK for Java 2.x는 런타임 (확인되지 않은) 예외를 사용합니다.

모든 SDK 예외를 포함하는 기본 예외는 Java 확인되지 않은 `RuntimeException`에서 확장된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/exception/SdkServiceException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/exception/SdkServiceException.html)입니다. 이를 포착하면 SDK에서 발생하는 모든 예외를 포착할 수 있습니다.

`SdkServiceException`에는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/awscore/exception/AwsServiceException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/awscore/exception/AwsServiceException.html)이라는 하위 클래스가 있습니다. 이 하위 클래스는 AWS 서비스와 통신할 때 문제가 발생했음을 나타냅니다. [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html)이라는 하위 클래스가 있는데, 이는 DynamoDB와의 통신에 문제가 있음을 나타냅니다. 이것을 포착하면 DynamoDB와 관련된 모든 예외를 포착할 수 있지만, 다른 SDK 예외는 포착하지 못합니다.

`DynamoDbException` 아래에는 더 구체적인 [예외 유형](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html)이 있습니다. 이러한 예외 유형 중 일부는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TableAlreadyExistsException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TableAlreadyExistsException.html)과 같은 컨트롤 플레인 작업에 적용됩니다. 다른 예외 유형은 데이터 영역 작업에 적용됩니다. 다음은 일반적인 데이터 영역 예외의 예입니다.
+ [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ConditionalCheckFailedException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ConditionalCheckFailedException.html) – 요청에서 false로 평가된 조건을 지정했습니다. 예를 들어 어떤 항목에서 조건부 업데이트 수행을 시도했지만 속성의 실제 값이 조건에서 예상되는 값과 일치하지 않았을 수 있습니다. 이러한 방식으로 실패한 요청은 재시도되지 않습니다.

다른 상황에서는 구체적인 예외가 정의되어 있지 않습니다. 예를 들어 요청이 제한되면 구체적인 `ProvisionedThroughputExceededException`이 발생하고 다른 경우에는 보다 일반적인 `DynamoDbException`이 발생할 수 있습니다. 어느 경우에나 `isThrottlingException()`이 `true`를 반환하는지 확인하여 제한으로 인해 예외가 발생했는지 확인할 수 있습니다.

애플리케이션 요구 사항에 따라 모든 `AwsServiceException` 또는 `DynamoDbException` 인스턴스를 포착할 수 있습니다. 그러나 상황에 따라 다른 동작이 필요한 경우가 많습니다. 상태 확인 실패를 처리하는 논리는 제한을 처리하는 것과 다릅니다. 처리할 예외적인 경로를 정의하고 대체 경로를 테스트해 보세요. 이를 통해 모든 관련 시나리오를 처리할 수 있습니다.

발생할 수 있는 일반적인 오류 목록은 [DynamoDB 관련 오류 처리](Programming.Errors.md) 섹션을 참조하세요. **Amazon DynamoDB API 참조의 [Common Errors](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/CommonErrors.html)도 참조하세요. 또한, API 참조는 각 API 작업(예: [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) 작업)에서 발생할 수 있는 정확한 오류를 제공합니다. 예외 처리에 대한 자세한 내용은 **AWS SDK for Java 2.x 개발자 안내서의 [Exception handling for the AWS SDK for Java 2.x](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/handling-exceptions.html)를 참조하세요.

## AWS 요청 ID
<a name="JavaRequestID"></a>

각 요청에는 요청 ID가 포함되어 있으며, 이 ID는 AWS Support과 협력하여 문제를 진단하는 경우 유용하게 사용할 수 있습니다. `SdkServiceException`에서 파생된 각 예외에는 요청 ID를 검색하는 데 사용할 수 있는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/exception/SdkServiceException.html#requestId()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/exception/SdkServiceException.html#requestId()) 메서드가 있습니다.

## 로깅
<a name="JavaLogging"></a>

SDK에서 제공하는 로깅을 사용하면 클라이언트 라이브러리에서 중요한 메시지를 포착하고 심층적으로 디버깅하는 데 모두 유용할 수 있습니다. 로거는 계층적이며 SDK는 `software.amazon.awssdk`를 루트 로거로 사용합니다. 수준은 `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `ALL`, `OFF` 중 하나로 구성할 수 있습니다. 구성된 수준은 해당 로거에 적용되며 로거 계층 구조까지 내려갑니다.

AWS SDK for Java 2.x에서는 로깅에 Simple Logging Façade for Java(SLF4J)를 사용합니다. 이는 다른 로거 주변의 추상화 계층 역할을 하며, 이를 사용하여 원하는 로거를 연결할 수 있습니다. 로거 연결에 대한 지침은 [SLF4J 사용 설명서](https://www.slf4j.org/manual.html)를 참조하세요.

각 로거에는 특정한 동작이 있습니다. 기본적으로 Log4j 2.x 로거는 로그 이벤트를 `System.out`에 추가하고 기본값은 `ERROR` 로그 수준에 추가하는 `ConsoleAppender`를 생성합니다.

SLF4J 내에 포함된 SimpleLogger 로거는 기본적으로 `System.err`을 출력하며 `INFO` 로그 수준을 기본값으로 사용합니다.

모든 프로덕션 배포에서 출력 수량을 제한하면서 SDK 클라이언트 라이브러리에서 중요한 메시지를 포착하려면 `software.amazon.awssdk` 수준을 `WARN`으로 설정하는 것이 좋습니다.

SLF4J가 클래스 경로에서 지원되는 로거를 찾을 수 없는 경우(SLF4J 바인딩 없음) 기본적으로 [무작업 구현](https://www.slf4j.org/codes.html#noProviders)으로 설정됩니다. 이 구현으로 인해 SLF4J가 클래스 경로에서 로거 구현을 찾을 수 없다는 내용의 메시지가 `System.err`에 로깅됩니다. 이러한 상황을 방지하려면 로거 구현을 추가해야 합니다. `org.slf4j.slf4j-simple` 또는 `org.apache.logging.log4j.log4j-slf4j2-imp`와 같은 아티팩트에 Apache Maven `pom.xml`의 종속성을 추가하면 됩니다.

애플리케이션 구성에 로깅 종속성을 추가하는 것을 포함하여 SDK에서 로깅을 구성하는 방법에 대한 자세한 내용은 **AWS SDK for Java 개발자 안내서의 [Logging with the SDK for Java 2.x](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/logging-slf4j.html)를 참조하세요.

`Log4j2.xml` 파일의 다음 구성은 Apache Log4j 2 로거를 사용하는 경우 로깅 동작을 조정하는 방법을 보여줍니다. 이 구성은 루트 로거 수준을 `WARN`으로 설정합니다. 계층의 모든 로거는 `software.amazon.awssdk` 로거를 포함하여 이 로그 수준을 상속합니다.

기본적으로 출력은 `System.out`으로 이동합니다. 다음 예시에서는 여전히 기본 출력 Log4j 어펜더를 재정의하여 맞춤형 Log4j `PatternLayout`을 적용합니다.

**`Log4j2.xml` 구성 파일 예**  
다음 구성은 모든 로거 계층 구조에 대해 `ERROR` 및 `WARN` 수준의 메시지를 콘솔에 로깅합니다.

```
<Configuration status="WARN">
  <Appenders>
    <Console name="ConsoleAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c:%L - %m%n" />
    </Console>
  </Appenders>

  <Loggers>
    <Root level="WARN">
      <AppenderRef ref="ConsoleAppender"/>
    </Root>
  </Loggers>
</Configuration>
```

### AWS 요청 ID 로깅
<a name="JavaReqIDLogging"></a>

문제가 발생한 경우 예외 내에서 요청 ID를 찾을 수 있습니다. 하지만 예외를 생성하지 않는 요청에 대한 요청 ID를 원하면 로깅을 사용하면 됩니다.

`software.amazon.awssdk.request` 로거는 `DEBUG` 수준에서 요청 ID를 출력합니다. 다음 예시는 이전 [configuration example](#Log4j2ConfigEg)를 확장하여 루트 로거 수준을 `ERROR`로, `software.amazon.awssdk`를 `WARN` 수준으로, `software.amazon.awssdk.request`를 `DEBUG` 수준으로 유지합니다. 이러한 수준을 설정하면 요청 ID 및 기타 요청 관련 세부 정보(예: 엔드포인트 및 상태 코드)를 파악하는 데 도움이 됩니다.

```
<Configuration status="WARN">
  <Appenders>
    <Console name="ConsoleAppender" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c:%L - %m%n" />
    </Console>
  </Appenders>

  <Loggers>
    <Root level="ERROR">
      <AppenderRef ref="ConsoleAppender"/>
    </Root>
    <Logger name="software.amazon.awssdk" level="WARN" />
    <Logger name="software.amazon.awssdk.request" level="DEBUG" />
  </Loggers>
</Configuration>
```

다음은 로그 출력의 예입니다:

```
2022-09-23 16:02:08 [main] DEBUG software.amazon.awssdk.request:85 - Sending Request: DefaultSdkHttpFullRequest(httpMethod=POST, protocol=https, host=dynamodb.us-east-1.amazonaws.com, encodedPath=/, headers=[amz-sdk-invocation-id, Content-Length, Content-Type, User-Agent, X-Amz-Target], queryParameters=[])
 2022-09-23 16:02:08 [main] DEBUG software.amazon.awssdk.request:85 - Received successful response: 200, Request ID: QS9DUMME2NHEDH8TGT9N5V53OJVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: not available
```

## 페이지 매김
<a name="JavaPagination"></a>

[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) 및 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html)과 같은 일부 요청의 경우 단일 요청에서 반환되는 데이터 크기가 제한되므로, 후속 페이지를 가져오려면 반복적으로 요청해야 합니다.

`Limit` 파라미터를 사용하여 각 페이지에 대해 읽을 항목의 최대 수를 제어할 수 있습니다. 예를 들어, `Limit` 파라미터를 사용하여 마지막 10개 항목만 검색할 수 있습니다. 이 제한은 필터링이 적용되기 전에 테이블에서 읽을 항목 수를 지정합니다. 필터링 후 정확히 10개를 원한다고 해서 지정할 수 있는 방법은 없습니다. 필터링 전의 개수를 제어하고 실제로 10개 항목을 검색한 경우에만 클라이언트 측에서 확인할 수 있습니다. 제한과 관계없이 응답의 최대 크기는 항상 1MB입니다.

API 응답에 `LastEvaluatedKey`가 포함될 수 있습니다. 이는 개수 제한 또는 크기 제한에 도달하여 응답이 종료되었음을 나타냅니다. 이 키는 해당 응답에 대해 마지막으로 평가된 키입니다. API와 직접 상호 작용하면 이 `LastEvaluatedKey`를 검색하고 `ExclusiveStartKey`로 후속 직접 호출에 전달하여 해당 시작 지점부터 다음 청크를 읽을 수 있습니다. `LastEvaluatedKey`가 반환되지 않으면 `Query` 또는 `Scan` API 직접 호출과 일치하는 항목이 더 이상 없는 것입니다.

다음 예시에서는 하위 수준 인터페이스를 사용하여 `keyConditionExpression` 파라미터에 따라 항목을 100개로 제한합니다.

```
QueryRequest.Builder queryRequestBuilder = QueryRequest.builder()
        .expressionAttributeValues(Map.of(
                ":pk_val", AttributeValue.fromS("123"),
                ":sk_val", AttributeValue.fromN("1000")))
        .keyConditionExpression("pk = :pk_val AND sk > :sk_val")
        .limit(100)
        .tableName(TABLE_NAME);

while (true) {
    QueryResponse queryResponse = DYNAMODB_CLIENT.query(queryRequestBuilder.build());

    queryResponse.items().forEach(item -> {
        LOGGER.info("item PK: [" + item.get("pk") + "] and SK: [" + item.get("sk") + "]");
    });

    if (!queryResponse.hasLastEvaluatedKey()) {
        break;
    }
    queryRequestBuilder.exclusiveStartKey(queryResponse.lastEvaluatedKey());
}
```

AWS SDK for Java 2.x는 자동으로 다음 결과 페이지를 얻기 위해 여러 서비스 직접 호출을 수행하는 자동 페이지 매김 메서드를 제공함으로써 DynamoDB와의 상호 작용을 단순화할 수 있습니다. 이렇게 하면 코드가 단순해지지만, 페이지를 수동으로 읽으면서 유지할 수 있는 리소스 사용에 대한 일부 제어 기능이 제거됩니다.

DynamoDB 클라이언트에서 사용할 수 있는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html#queryPaginator(software.amazon.awssdk.services.dynamodb.model.QueryRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html#queryPaginator(software.amazon.awssdk.services.dynamodb.model.QueryRequest)) 및 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html#scanPaginator(software.amazon.awssdk.services.dynamodb.model.ScanRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html#scanPaginator(software.amazon.awssdk.services.dynamodb.model.ScanRequest))와 같은 `Iterable` 메서드를 사용하여 SDK가 페이지 매김을 처리합니다. 이러한 메서드의 반환 유형은 모든 페이지를 반복하는 데 사용할 수 있는 사용자 지정 반복자입니다. SDK는 내부적으로 서비스 직접 호출을 처리합니다. Java Stream API를 사용하여 다음 예제와 같이 `QueryPaginator`의 결과를 처리할 수 있습니다.

```
QueryPublisher queryPublisher =
    DYNAMODB_CLIENT.queryPaginator(QueryRequest.builder()
        .expressionAttributeValues(Map.of(
            ":pk_val", AttributeValue.fromS("123"),
            ":sk_val", AttributeValue.fromN("1000")))
        .keyConditionExpression("pk = :pk_val AND sk > :sk_val")
        .limit(100)
        .tableName("YourTableName")
        .build());

queryPublisher.items().subscribe(item ->
    System.out.println(item.get("itemData"))).join();
```

## 데이터 클래스 주석
<a name="JavaDataClassAnnotation"></a>

Java SDK는 데이터 클래스의 속성에 적용할 수 있는 여러 주석을 제공합니다. 이러한 주석은 SDK가 속성과 상호 작용하는 방식에 영향을 줍니다. 주석을 추가하면 속성이 암시적 원자성 카운터 역할을 하도록 하거나, 자동 생성된 타임스탬프 값을 유지하거나, 항목 버전 번호를 추적할 수 있습니다. 자세한 내용은 [Data class annotations](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)를 참조하세요.