

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

# AWS SDK for Java 2.x에서 AWS 서비스 호출
<a name="work-with-services"></a>

이 섹션에서는 간단한 자습서와 일부 AWS 서비스와의 작업 방법에 대한 가이드를 제공합니다. 전체 예제 세트는 [코드 예제 섹션](java_code_examples.md)을 참조하세요.

**Topics**
+ [CloudWatch](examples-cloudwatch.md)
+ [AWS 데이터베이스 서비스](examples-databases.md)
+ [DynamoDB](examples-dynamodb.md)
+ [Amazon EC2](examples-ec2.md)
+ [IAM](examples-iam.md)
+ [Kinesis](examples-kinesis.md)
+ [Lambda](examples-lambda.md)
+ [Amazon S3](examples-s3.md)
+ [Amazon SNS](examples-simple-notification-service.md)
+ [Amazon SQS](examples-sqs.md)
+ [Amazon Transcribe](examples-transcribe.md)

# 작업 CloudWatch
<a name="examples-cloudwatch"></a>

이 섹션에서는 AWS SDK for Java 2.x를 사용하여 [Amazon CloudWatch](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html)를 프로그래밍하는 예제를 제공합니다.

 Amazon CloudWatch 는 Amazon Web Services (AWS) 리소스와 AWS 실행 중인 애플리케이션을 실시간으로 모니터링합니다. CloudWatch 를 사용하여 리소스 및 애플리케이션에 대해 측정할 수 있는 변수인 지표를 수집하고 추적할 수 있습니다. CloudWatch 경보는 사용자가 정의한 규칙에 따라 모니터링 중인 리소스를 자동으로 변경하거나 알림을 보냅니다.

다음 예제에는 각 기술을 보여주는 데 필요한 코드만 포함되어 있습니다. [전체 예제 코드는 GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)에 있습니다. 이 위치에서 단일 소스 파일을 다운로드하거나 리포지토리를 로컬로 복사하여 모든 예제를 빌드하고 실행할 수 있습니다.

**Topics**
+ [에서 지표 가져오기 CloudWatch](examples-cloudwatch-get-metrics.md)
+ [사용자 지정 지표 데이터를에 게시 CloudWatch](examples-cloudwatch-publish-custom-metrics.md)
+ [CloudWatch 경보 작업](examples-cloudwatch-create-alarms.md)
+ [Amazon CloudWatch Events 사용](examples-cloudwatch-send-events.md)

# 에서 지표 가져오기 CloudWatch
<a name="examples-cloudwatch-get-metrics"></a>

## 지표 나열
<a name="listing-metrics"></a>

 CloudWatch 지표를 나열하려면 [ListMetricsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/ListMetricsRequest.html)를 생성하고 CloudWatchClient의 `listMetrics` 메서드를 호출합니다. `ListMetricsRequest`를 사용하여 반환된 지표를 네임스페이스, 지표 이름 또는 차원을 기준으로 필터링할 수 있습니다.

**참고**  
 AWS 서비스에서 게시하는 지표 및 차원 목록은 Amazon CloudWatch 사용 설명서의 [Amazon CloudWatch 지표 및 차원 참조](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html)에서 확인할 수 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatch.model.ListMetricsRequest;
import software.amazon.awssdk.services.cloudwatch.model.ListMetricsResponse;
import software.amazon.awssdk.services.cloudwatch.model.Metric;
```

 **코드** 

```
    public static void listMets( CloudWatchClient cw, String namespace) {

        boolean done = false;
        String nextToken = null;

        try {
            while(!done) {

                ListMetricsResponse response;

                if (nextToken == null) {
                   ListMetricsRequest request = ListMetricsRequest.builder()
                        .namespace(namespace)
                        .build();

                 response = cw.listMetrics(request);
                } else {
                  ListMetricsRequest request = ListMetricsRequest.builder()
                        .namespace(namespace)
                        .nextToken(nextToken)
                        .build();

                response = cw.listMetrics(request);
            }

            for (Metric metric : response.metrics()) {
                System.out.printf(
                        "Retrieved metric %s", metric.metricName());
                System.out.println();
            }

            if(response.nextToken() == null) {
                done = true;
            } else {
                nextToken = response.nextToken();
            }
        }

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

지표는 해당 `getMetrics` 메서드를 호출하여 [ListMetricsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/ListMetricsResponse.html)에 반환됩니다.

결과를 *페이징*할 수 있습니다. 다음 결과 배치를 검색하려면 응답 객체에서 `nextToken`를 호출하고 토큰 값을 사용하여 새 요청 객체를 빌드합니다. 그런 다음 새 요청을 사용해 다시 `listMetrics` 메서드를 호출합니다.

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

## 추가 정보
<a name="more-information"></a>
+  Amazon CloudWatch API 참조의 [ListMetrics](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_ListMetrics.html) 

# 사용자 지정 지표 데이터를에 게시 CloudWatch
<a name="examples-cloudwatch-publish-custom-metrics"></a>

여러 AWS 서비스가 " `AWS`"로 시작하는 네임스페이스에 [자체 지표](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html)를 게시합니다. 자체 네임스페이스를 사용하여 사용자 지정 지표 데이터를 게시할 수도 있습니다(" "로 시작하지 않는 한`AWS`).

## 사용자 지정 지표 데이터 게시
<a name="cwid1"></a>

자체 지표 데이터를 게시하려면 [PutMetricDataRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/PutMetricDataRequest.html)를 사용하여 CloudWatchClient의 `putMetricData` 메서드를 호출하세요. `PutMetricDataRequest`는 데이터에 사용할 사용자 지정 네임스페이스와, [MetricDatum](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/MetricDatum.html) 객체의 데이터 포인트 자체에 대한 정보를 포함해야 합니다.

**참고**  
" `AWS` "로 시작하는 네임스페이스는 지정할 수 없습니다. " `AWS`"로 시작하는 네임스페이스는 Amazon Web Services 제품용으로 예약되어 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.MetricDatum;
import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataRequest;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
```

 **코드** 

```
    public static void putMetData(CloudWatchClient cw, Double dataPoint ) {

        try {
            Dimension dimension = Dimension.builder()
                    .name("UNIQUE_PAGES")
                    .value("URLS")
                    .build();

            // Set an Instant object
            String time = ZonedDateTime.now( ZoneOffset.UTC ).format( DateTimeFormatter.ISO_INSTANT );
            Instant instant = Instant.parse(time);

            MetricDatum datum = MetricDatum.builder()
                .metricName("PAGES_VISITED")
                .unit(StandardUnit.NONE)
                .value(dataPoint)
                .timestamp(instant)
                .dimensions(dimension).build();

            PutMetricDataRequest request = PutMetricDataRequest.builder()
                .namespace("SITE/TRAFFIC")
                .metricData(datum).build();

            cw.putMetricData(request);

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        System.out.printf("Successfully put data point %f", dataPoint);
     }
```

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

## 추가 정보
<a name="more-information"></a>
+  Amazon CloudWatch 사용 설명서의 [Amazon CloudWatch 지표를 사용합니다](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/working_with_metrics.html).
+  Amazon CloudWatch 사용 설명서의 [AWS 네임스페이스](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html).
+  Amazon CloudWatch API 참조의 [PutMetricData](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_PutMetricData.html).

# CloudWatch 경보 작업
<a name="examples-cloudwatch-create-alarms"></a>

## 경보 만들기
<a name="create-an-alarm"></a>

 CloudWatch 지표를 기반으로 경보를 생성하려면 경보 조건으로 채워진 [PutMetricAlarmRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/PutMetricAlarmRequest.html)를 사용하여 CloudWatchClient의 `putMetricAlarm` 메서드를 호출합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.PutMetricAlarmRequest;
import software.amazon.awssdk.services.cloudwatch.model.ComparisonOperator;
import software.amazon.awssdk.services.cloudwatch.model.Statistic;
import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
```

 **코드** 

```
    public static void putMetricAlarm(CloudWatchClient cw, String alarmName, String instanceId) {

        try {
            Dimension dimension = Dimension.builder()
                .name("InstanceId")
                .value(instanceId).build();

            PutMetricAlarmRequest request = PutMetricAlarmRequest.builder()
                .alarmName(alarmName)
                .comparisonOperator(
                        ComparisonOperator.GREATER_THAN_THRESHOLD)
                .evaluationPeriods(1)
                .metricName("CPUUtilization")
                .namespace("AWS/EC2")
                .period(60)
                .statistic(Statistic.AVERAGE)
                .threshold(70.0)
                .actionsEnabled(false)
                .alarmDescription(
                        "Alarm when server CPU utilization exceeds 70%")
                .unit(StandardUnit.SECONDS)
                .dimensions(dimension)
                .build();

            cw.putMetricAlarm(request);
            System.out.printf(
                    "Successfully created alarm with name %s", alarmName);

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 경보 나열
<a name="list-alarms"></a>

생성한 CloudWatch 경보를 나열하려면 결과에 대한 옵션을 설정하는 데 사용할 수 있는 [DescribeAlarmsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/DescribeAlarmsRequest.html)를 사용하여 CloudWatchClient의 `describeAlarms` 메서드를 호출합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatch.model.DescribeAlarmsRequest;
import software.amazon.awssdk.services.cloudwatch.model.DescribeAlarmsResponse;
import software.amazon.awssdk.services.cloudwatch.model.MetricAlarm;
```

 **코드** 

```
    public static void desCWAlarms( CloudWatchClient cw) {

        try {

            boolean done = false;
            String newToken = null;

            while(!done) {
                DescribeAlarmsResponse response;

                if (newToken == null) {
                    DescribeAlarmsRequest request = DescribeAlarmsRequest.builder().build();
                    response = cw.describeAlarms(request);
                } else {
                    DescribeAlarmsRequest request = DescribeAlarmsRequest.builder()
                        .nextToken(newToken)
                        .build();
                    response = cw.describeAlarms(request);
                }

                for(MetricAlarm alarm : response.metricAlarms()) {
                    System.out.printf("\n Retrieved alarm %s", alarm.alarmName());
                }

                if(response.nextToken() == null) {
                    done = true;
                } else {
                    newToken = response.nextToken();
                }
            }

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        System.out.printf("Done");
    }
```

`describeAlarms`에 의해 반환되는 [DescribeAlarmsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/DescribeAlarmsResponse.html)에 대해 `MetricAlarms`를 호출하여 경보 목록을 가져올 수 있습니다.

결과를 *페이징*할 수 있습니다. 다음 결과 배치를 검색하려면 응답 객체에서 `nextToken`를 호출하고 토큰 값을 사용하여 새 요청 객체를 빌드합니다. 그런 다음 새 요청을 사용해 다시 `describeAlarms` 메서드를 호출합니다.

**참고**  
CloudWatchClient의 `describeAlarmsForMetric` 메서드를 사용하여 특정 지표의 경보를 검색할 수도 있습니다. 이 메서드의 용도는 `describeAlarms`와 비슷합니다.

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

## 경보 삭제
<a name="delete-alarms"></a>

 CloudWatch 경보를 삭제하려면 삭제하려는 하나 이상의 경보 이름이 포함된 [DeleteAlarmsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/DeleteAlarmsRequest.html)를 사용하여 CloudWatchClient의 `deleteAlarms` 메서드를 호출합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatch.model.DeleteAlarmsRequest;
```

 **코드** 

```
    public static void deleteCWAlarm(CloudWatchClient cw, String alarmName) {

        try {
            DeleteAlarmsRequest request = DeleteAlarmsRequest.builder()
                    .alarmNames(alarmName)
                    .build();

            cw.deleteAlarms(request);
            System.out.printf("Successfully deleted alarm %s", alarmName);

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 추가 정보
<a name="more-information"></a>
+  Amazon CloudWatch 사용 설명서의 [Amazon CloudWatch 경보 사용](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html) 
+  Amazon CloudWatch API 참조의 [PutMetricAlarm](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_PutMetricAlarm.html) 
+  Amazon CloudWatch API 참조의 [DescribeAlarms](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_DescribeAlarms.html) 
+  Amazon CloudWatch API 참조의 [DeleteAlarms](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_DeleteAlarms.html) 

# Amazon CloudWatch Events 사용
<a name="examples-cloudwatch-send-events"></a>

 CloudWatch Events는 Amazon EC2 인스턴스, Lambda 함수, Kinesis 스트림, Amazon ECS 작업, Step Functions 상태 시스템, Amazon SNS 주제, Amazon SQS 대기열 또는 내장 대상에 대한 AWS 리소스 변경을 설명하는 시스템 이벤트의 스트림을 거의 실시간으로 제공합니다. 단순 규칙을 사용하여 일치하는 이벤트를 검색하고 하나 이상의 대상 함수 또는 스트림으로 이를 라우팅할 수 있습니다.

Amazon EventBridge는 CloudWatch Events의 [업그레이드](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-cwe-now-eb.html) 버전입니다. 두 서비스 모두 동일한 API를 사용하므로 SDK에서 제공하는 [CloudWatch Events 클라이언트](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/CloudWatchClient.html)를 계속 사용하거나 CloudWatch Events 기능을 위해 SDK for Java의 [EventBridge 클라이언트](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/eventbridge/EventBridgeClient.html)로 마이그레이션할 수 있습니다. 이제 EventBridge 설명서 사이트를 통해 CloudWatch Events [사용 설명서](https://docs.aws.amazon.com/eventbridge/latest/userguide/index.html) 및 [API 참조](https://docs.aws.amazon.com/eventbridge/latest/APIReference/index.html)를 사용할 수 있습니다.

## 이벤트 추가
<a name="add-events"></a>

사용자 지정 CloudWatch 이벤트를 추가하려면 각 이벤트에 대한 세부 정보를 제공하는 하나 이상의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequest.html) 객체가 포함된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequestEntry.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequestEntry.html) 객체를 사용하여 `CloudWatchEventsClient’s` `putEvents` 메서드를 호출합니다. 이벤트 유형 및 소스, 이벤트와 연결된 리소스 등 입력 항목에 대한 여러 파라미터를 지정할 수 있습니다.

**참고**  
`putEvents` 호출당 최대 10개 이벤트를 지정할 수 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient;
import software.amazon.awssdk.services.cloudwatchevents.model.PutEventsRequest;
import software.amazon.awssdk.services.cloudwatchevents.model.PutEventsRequestEntry;
```

 **코드** 

```
    public static void putCWEvents(CloudWatchEventsClient cwe, String resourceArn ) {

        try {

            final String EVENT_DETAILS =
                "{ \"key1\": \"value1\", \"key2\": \"value2\" }";

            PutEventsRequestEntry requestEntry = PutEventsRequestEntry.builder()
                    .detail(EVENT_DETAILS)
                    .detailType("sampleSubmitted")
                    .resources(resourceArn)
                    .source("aws-sdk-java-cloudwatch-example")
                    .build();

            PutEventsRequest request = PutEventsRequest.builder()
                    .entries(requestEntry)
                    .build();

            cwe.putEvents(request);
            System.out.println("Successfully put CloudWatch event");

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 규칙 추가
<a name="add-rules"></a>

규칙을 생성하거나 업데이트하려면 규칙 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutRuleRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutRuleRequest.html) 이름과 [이벤트 패턴](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html), 규칙과 연결할 IAM 역할, 규칙 실행 빈도를 설명하는 [예약 표현](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html)식과 같은 선택적 파라미터를 사용하여 `CloudWatchEventsClient’s` `putRule` 메서드를 호출합니다.

 **가져오기** 

```
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient;
import software.amazon.awssdk.services.cloudwatchevents.model.PutRuleRequest;
import software.amazon.awssdk.services.cloudwatchevents.model.PutRuleResponse;
import software.amazon.awssdk.services.cloudwatchevents.model.RuleState;
```

 **코드** 

```
    public static void putCWRule(CloudWatchEventsClient cwe, String ruleName, String roleArn) {

        try {
            PutRuleRequest request = PutRuleRequest.builder()
                .name(ruleName)
                .roleArn(roleArn)
                .scheduleExpression("rate(5 minutes)")
                .state(RuleState.ENABLED)
                .build();

            PutRuleResponse response = cwe.putRule(request);
            System.out.printf(
                    "Successfully created CloudWatch events rule %s with arn %s",
                    roleArn, response.ruleArn());
        } catch (
            CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 대상 추가
<a name="add-targets"></a>

대상은 규칙이 트리거될 때 호출되는 리소스입니다. 예제 대상에는 Amazon EC2 인스턴스, Lambda 함수, Kinesis 스트림, Amazon ECS 작업, Step Functions 상태 시스템 및 기본 제공 대상이 포함됩니다.

규칙에 대상을 추가하려면 업데이트할 규칙과 규칙에 추가할 대상 목록이 포함된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutTargetsRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutTargetsRequest.html)를 사용하여 `CloudWatchEventsClient’s` `putTargets` 메서드를 직접적으로 호출합니다.

 **가져오기** 

```
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient;
import software.amazon.awssdk.services.cloudwatchevents.model.PutTargetsRequest;
import software.amazon.awssdk.services.cloudwatchevents.model.PutTargetsResponse;
import software.amazon.awssdk.services.cloudwatchevents.model.Target;
```

 **코드** 

```
    public static void putCWTargets(CloudWatchEventsClient cwe, String ruleName, String functionArn, String targetId ) {

        try {
            Target target = Target.builder()
                .arn(functionArn)
                .id(targetId)
                .build();

            PutTargetsRequest request = PutTargetsRequest.builder()
                .targets(target)
                .rule(ruleName)
                .build();

            PutTargetsResponse response = cwe.putTargets(request);
            System.out.printf(
                "Successfully created CloudWatch events target for rule %s",
                ruleName);
        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 추가 정보
<a name="more-information"></a>
+  Amazon EventBridge 사용 설명서의 [PutEvents와 함께 이벤트 추가](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevents.html)
+  Amazon EventBridge 사용 설명서의 [규칙에 대한 스케줄 표현식](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html#eb-create-scheduled-rule-schedule)
+  Amazon EventBridge 사용 설명서의 [CloudWatch Events이벤트의 이벤트 유형](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-service-event.html)
+  Amazon EventBridge 사용 설명서의 [이벤트 패턴](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html)
+  Amazon EventBridge API 참조의 [PutEvents](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html)
+  Amazon EventBridge API 참조의 [PutTargets](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutTargets.html)
+  Amazon EventBridge API 참조의 [PutRule](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutRule.html)

# AWS 데이터베이스 서비스 및 AWS SDK for Java 2.x
<a name="examples-databases"></a>

AWS는 관계형, 키-값, 인메모리, 문서 및 [기타 여러](https://aws.amazon.com/products/databases/) 데이터베이스 유형을 제공합니다. Java 2.x용 SDK 지원은 AWS에서 데이터베이스 서비스의 특성에 따라 달라집니다.

[Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/Welcome.html) 서비스와 같은 일부 데이터베이스 서비스에는 AWS 리소스(데이터베이스)를 관리하는 웹 서비스 API와 데이터와 상호 작용하는 웹 서비스 API가 있습니다. Java 2.x용 SDK에서 이러한 유형의 서비스에는 [DynamoDBClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html)와 같은 전용 서비스 클라이언트가 있습니다.

다른 데이터베이스 서비스에는 [Amazon DocumentDB](https://docs.aws.amazon.com/documentdb/latest/developerguide/api-reference.html) API(클러스터, 인스턴스 및 리소스 관리용)와 같이 리소스와 상호 작용하는 웹 서비스 API가 있지만 데이터 작업을 위한 웹 서비스 API는 없습니다. Java 2.x용 SDK에는 리소스 작업을 위한 해당 [DocDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/docdb/DocDbClient.html) 인터페이스가 있습니다. 그러나 데이터를 처리하려면 [Java용 MongoDB](https://www.mongodb.com/developer/languages/java/)와 같은 다른 Java API가 필요합니다.

아래 예를 사용하여 다양한 유형의 데이터베이스와 함께 Java 2.x 서비스 클라이언트용 SDK를 사용하는 방법을 알아보세요.

## Amazon DynamoDB 예제
<a name="examples-db-dynamodb"></a>


| 데이터 작업 | 데이터베이스 작업 | 
| --- |--- |
| SDK service client: [DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html) | SDK service client: [DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html) | 
| Example: [DynamoDB를 사용하는 React 및 Spring REST 애플리케이션](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/creating_dynamodb_web_app) | Examples: [CreateTable, ListTables, DeleteTable](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb) | 
| Examples: [여러 DynamoDB 예제](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb) |  | 
|  | 
| --- |
| SDK service client: [DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html) |  | 
| Example: [DynamoDB를 사용하는 React 및 Spring REST 애플리케이션](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/creating_dynamodb_web_app) |  | 
| Examples: [여러 DynamoDB 예제](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb) (names starting with 'Enhanced") |  | 

이 가이드의 가이드 코드 예제 단원에서 [추가 DynamoDB 예제](examples-dynamodb.md)를 참조하세요.

## Amazon RDS 예제
<a name="examples-db-rds"></a>


|  데이터 작업  |  데이터베이스 작업  | 
| --- | --- | 
| 비 SDK API: JDBC, 데이터베이스별 SQL 버전. 사용자 코드가 데이터베이스 연결 또는 연결 풀을 관리합니다. | SDK 서비스 클라이언트: [RdsClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/rds/RdsClient.html) | 
| 예: [MySQL을 사용한 React 및 Spring REST 애플리케이션](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/Creating_rds_item_tracker) | 예: [여러 RdsClient 예제](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/rds/src/main/java/com/example/rds) | 

## Amazon Redshift 예제
<a name="examples-db-redshift"></a>


|  데이터 작업  |  데이터베이스 작업  | 
| --- | --- | 
| SDK 서비스 클라이언트: [RedshiftDataClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/redshiftdata/RedshiftDataClient.html) | SDK 서비스 클라이언트: [RedshiftClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/redshift/RedshiftClient.html) | 
| 예: [여러 RedshiftDataClient 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/c682a07a1e6abce793e3c32ef3b9661fa723d0ff/javav2/example_code/redshift/src/main/java/com/example/scenario/RedshiftScenario.java) | 예: [여러 RedshiftClient 예제](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/redshift/src/main/java/com/example/redshift) | 
| 예: [RedshiftDataClient를 사용하는 React 및 Spring REST 애플리케이션](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/CreatingSpringRedshiftRest) |  | 

## Amazon Aurora Serverless v2 예제
<a name="examples-db-aurora-sv1"></a>


|  데이터 작업  |  데이터베이스 작업  | 
| --- | --- | 
| SDK 서비스 클라이언트: [RdsDataClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/rdsdata/RdsDataClient.html) | SDK 서비스 클라이언트: [RdsClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/rds/RdsClient.html) | 
| 예: [RdsDataClient를 사용하는 React 및 Spring REST 애플리케이션](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/Creating_Spring_RDS_Rest) | 예: [여러 RdsClient 예제](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/rds/src/main/java/com/example/rds) | 

## Amazon DocumentDB 예제
<a name="examples-db-docdb"></a>


|  데이터 작업  |  데이터베이스 작업  | 
| --- | --- | 
| 비 SDK API: MongoDB 전용 자바 라이브러리(예: [Java용 MongoDB](https://www.mongodb.com/developer/languages/java/)). 사용자 코드가 데이터베이스 연결 또는 연결 풀을 관리합니다. | SDK 서비스 클라이언트: [DocDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/docdb/DocDbClient.html) | 
| 예: [DocumentDB(Mongo) 개발자 가이드 (](https://docs.aws.amazon.com/documentdb/latest/developerguide/connect_programmatically.html#connect_programmatically-tls_enabled)'Java' 탭 선택) |  | 

# 작업 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`를 대신 사용합니다.

# Amazon EC2 작업
<a name="examples-ec2"></a>

이 단원에서는 AWS SDK for Java 2.x를 사용한 [Amazon EC2](https://docs.aws.amazon.com/ec2/) 프로그래밍의 예제를 제공합니다.

**Topics**
+ [Amazon EC2 인스턴스 관리](examples-ec2-instances.md)
+ [AWS 리전 및 가용 영역 사용](examples-ec2-regions-zones.md)
+ [Amazon EC2의 보안 그룹 작업](examples-ec2-security-groups.md)
+ [Amazon EC2 인스턴스 메타데이터 작업](examples-ec2-IMDS.md)

# Amazon EC2 인스턴스 관리
<a name="examples-ec2-instances"></a>

## 인스턴스 생성
<a name="create-an-instance"></a>

[Ec2Client](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html)의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#runInstances(software.amazon.awssdk.services.ec2.model.RunInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#runInstances(software.amazon.awssdk.services.ec2.model.RunInstancesRequest)) 메서드를 호출하여 사용할 [Amazon Machine Image(AMI)](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/AMIs.html)와 Amazon EC2 인스턴스 유형을 포함하는 [RunInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/RunInstancesRequest.html)를 제공하여 새 인스턴스를 생성합니다. [https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/instance-types.html](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/instance-types.html) 

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.InstanceType;
import software.amazon.awssdk.services.ec2.model.RunInstancesRequest;
import software.amazon.awssdk.services.ec2.model.RunInstancesResponse;
import software.amazon.awssdk.services.ec2.model.Tag;
import software.amazon.awssdk.services.ec2.model.CreateTagsRequest;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 **코드** 

```
   public static String createEC2Instance(Ec2Client ec2,String name, String amiId ) {

        RunInstancesRequest runRequest = RunInstancesRequest.builder()
                .imageId(amiId)
                .instanceType(InstanceType.T1_MICRO)
                .maxCount(1)
                .minCount(1)
                .build();

        RunInstancesResponse response = ec2.runInstances(runRequest);
        String instanceId = response.instances().get(0).instanceId();

        Tag tag = Tag.builder()
                .key("Name")
                .value(name)
                .build();

        CreateTagsRequest tagRequest = CreateTagsRequest.builder()
                .resources(instanceId)
                .tags(tag)
                .build();

        try {
            ec2.createTags(tagRequest);
            System.out.printf(
                    "Successfully started EC2 Instance %s based on AMI %s",
                    instanceId, amiId);

          return instanceId;

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }

        return "";
    }
```

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

## 인스턴스 시작
<a name="start-an-instance"></a>

 Amazon EC2 인스턴스를 시작하려면 Ec2Client의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#startInstances(software.amazon.awssdk.services.ec2.model.StartInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#startInstances(software.amazon.awssdk.services.ec2.model.StartInstancesRequest)) 메서드를 호출하여 시작할 인스턴스의 ID가 포함된 [StartInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/StartInstancesRequest.html)를 제공합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.StartInstancesRequest;
import software.amazon.awssdk.services.ec2.model.StopInstancesRequest;
```

 **코드** 

```
    public static void startInstance(Ec2Client ec2, String instanceId) {

        StartInstancesRequest request = StartInstancesRequest.builder()
                .instanceIds(instanceId)
                .build();

        ec2.startInstances(request);
        System.out.printf("Successfully started instance %s", instanceId);
    }
```

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

## 인스턴스 중지
<a name="stop-an-instance"></a>

 Amazon EC2 인스턴스를 중지하려면 Ec2Client의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#stopInstances(software.amazon.awssdk.services.ec2.model.StopInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#stopInstances(software.amazon.awssdk.services.ec2.model.StopInstancesRequest)) 메서드를 호출하여 중지할 인스턴스의 ID가 포함된 [StopInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/StopInstancesRequest.html)를 제공합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.StartInstancesRequest;
import software.amazon.awssdk.services.ec2.model.StopInstancesRequest;
```

 **코드** 

```
    public static void stopInstance(Ec2Client ec2, String instanceId) {

        StopInstancesRequest request = StopInstancesRequest.builder()
                .instanceIds(instanceId)
                .build();

        ec2.stopInstances(request);
        System.out.printf("Successfully stopped instance %s", instanceId);
    }
```

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

## 인스턴스 재부팅
<a name="reboot-an-instance"></a>

 Amazon EC2 인스턴스를 재부팅하려면 Ec2Client의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#rebootInstances(software.amazon.awssdk.services.ec2.model.RebootInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#rebootInstances(software.amazon.awssdk.services.ec2.model.RebootInstancesRequest)) 메서드를 호출하여 재부팅할 인스턴스의 ID가 포함된 [RebootInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/RebootInstancesRequest.html)를 제공합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
import software.amazon.awssdk.services.ec2.model.RebootInstancesRequest;
```

 **코드** 

```
    public static void rebootEC2Instance(Ec2Client ec2, String instanceId) {

      try {
            RebootInstancesRequest request = RebootInstancesRequest.builder()
                .instanceIds(instanceId)
                    .build();

            ec2.rebootInstances(request);
            System.out.printf(
                "Successfully rebooted instance %s", instanceId);
    } catch (Ec2Exception e) {
          System.err.println(e.awsErrorDetails().errorMessage());
          System.exit(1);
     }
  }
```

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

## 인스턴스 설명
<a name="describe-instances"></a>

인스턴스를 나열하려면 [DescribeInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeInstancesRequest.html)를 생성하고 Ec2Client의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#describeInstances(software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#describeInstances(software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest)) 메서드를 호출합니다. 계정 및 리전의 Amazon EC2 인스턴스를 나열하는 데 사용할 수 있는 [DescribeInstancesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeInstancesResponse.html) 객체를 반환합니다.

인스턴스는 *예약*별로 그룹화됩니다. 각 예약은 인스턴스를 시작하는 `startInstances` 호출에 해당합니다. 인스턴스를 나열하려면 먼저 `DescribeInstancesResponse` 클래스의 `reservations`를 호출하고 반환된 각 [Reservation](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/Reservation.html) 객체에서 `instances`를 호출해야 합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse;
import software.amazon.awssdk.services.ec2.model.Instance;
import software.amazon.awssdk.services.ec2.model.Reservation;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 **코드** 

```
    public static void describeEC2Instances( Ec2Client ec2){

        String nextToken = null;

        try {

            do {
                DescribeInstancesRequest request = DescribeInstancesRequest.builder().maxResults(6).nextToken(nextToken).build();
                DescribeInstancesResponse response = ec2.describeInstances(request);

                for (Reservation reservation : response.reservations()) {
                    for (Instance instance : reservation.instances()) {
                        System.out.println("Instance Id is " + instance.instanceId());
                        System.out.println("Image id is "+  instance.imageId());
                        System.out.println("Instance type is "+  instance.instanceType());
                        System.out.println("Instance state name is "+  instance.state().name());
                        System.out.println("monitoring information is "+  instance.monitoring().state());

                }
            }
                nextToken = response.nextToken();
            } while (nextToken != null);

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

결과가 페이징됩니다. 결과 객체의 `nextToken` 메서드에서 반환된 값을 새 요청 객체의 `nextToken` 메서드에 전달하고 다음 번 `describeInstances` 호출의 새 요청 객체를 사용함으로써 추가 결과를 가져올 수 있습니다.

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

## 인스턴스 모니터링
<a name="monitor-an-instance"></a>

CPU 및 네트워크 사용률, 사용 가능한 메모리, 남은 디스크 공간 등 Amazon EC2 인스턴스의 다양한 측면을 모니터링할 수 있습니다. 인스턴스 모니터링에 대한 자세한 내용은 Linux 인스턴스용 Amazon EC2 사용 설명서의 [모니터링을 Amazon EC2](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/monitoring_ec2.html) 참조하세요.

인스턴스 모니터링을 시작하려면 모니터링할 인스턴스의 ID로 [MonitorInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/MonitorInstancesRequest.html)를 생성하고 Ec2Client의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#monitorInstances(software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#monitorInstances(software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest)) 메서드에 전달합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest;
import software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest;
```

 **코드** 

```
    public static void monitorInstance( Ec2Client ec2, String instanceId) {

        MonitorInstancesRequest request = MonitorInstancesRequest.builder()
                .instanceIds(instanceId).build();

        ec2.monitorInstances(request);
        System.out.printf(
                "Successfully enabled monitoring for instance %s",
                instanceId);
    }
```

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

## 인스턴스 모니터링 중지
<a name="stop-instance-monitoring"></a>

인스턴스 모니터링을 중지하려면 모니터링을 중지할 인스턴스의 ID로 [UnmonitorInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/UnmonitorInstancesRequest.html)를 생성하고 Ec2Client의 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#unmonitorInstances(software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#unmonitorInstances(software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest)) 메서드에 전달합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest;
import software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest;
```

 **코드** 

```
    public static void unmonitorInstance(Ec2Client ec2, String instanceId) {
        UnmonitorInstancesRequest request = UnmonitorInstancesRequest.builder()
                .instanceIds(instanceId).build();

        ec2.unmonitorInstances(request);

        System.out.printf(
                "Successfully disabled monitoring for instance %s",
                instanceId);
    }
```

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

## 추가 정보
<a name="more-information"></a>
+  Amazon EC2 API 참조의 [RunInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html) 
+  Amazon EC2 API 참조의 [DescribeInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html) 
+  Amazon EC2 API 참조의 [StartInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StartInstances.html) 
+  Amazon EC2 API 참조의 [StopInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StopInstances.html) 
+  Amazon EC2 API 참조의 [RebootInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RebootInstances.html) 
+  Amazon EC2 API 참조의 [MonitorInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_MonitorInstances.html) 
+  Amazon EC2 API 참조의 [UnmonitorInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_UnmonitorInstances.html) 

# AWS 리전 및 가용 영역 사용
<a name="examples-ec2-regions-zones"></a>

## 리전 설명
<a name="describe-regions"></a>

계정에 사용할 수 있는 리전을 나열하려면 Ec2Client의 `describeRegions` 메서드를 호출합니다. 이 메서드는 [DescribeRegionsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeRegionsResponse.html)를 반환합니다. 반환된 객체의 `regions` 메서드를 호출하여 각 리전을 나타내는 [Region](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/Region.html) 객체의 목록을 가져옵니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;
```

 **코드** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class DescribeRegionsAndZones {
    public static void main(String[] args) {
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();

        try {
            CompletableFuture<Void> future = describeEC2RegionsAndZonesAsync(ec2AsyncClient);
            future.join(); // Wait for both async operations to complete.
        } catch (RuntimeException rte) {
            System.err.println("An exception occurred: " + (rte.getCause() != null ? rte.getCause().getMessage() : rte.getMessage()));
        }
    }

    /**
     * Asynchronously describes the EC2 regions and availability zones.
     *
     * @param ec2AsyncClient the EC2 async client used to make the API calls
     * @return a {@link CompletableFuture} that completes when both the region and availability zone descriptions are complete
     */
    public static CompletableFuture<Void> describeEC2RegionsAndZonesAsync(Ec2AsyncClient ec2AsyncClient) {
        // Initiate the asynchronous request to describe regions
        CompletableFuture<DescribeRegionsResponse> regionsResponse = ec2AsyncClient.describeRegions();

        // Handle the response or exception for regions
        CompletableFuture<DescribeRegionsResponse> regionsFuture = regionsResponse.whenComplete((regionsResp, ex) -> {
            if (ex != null) {
                // Handle the exception by throwing a RuntimeException
                throw new RuntimeException("Failed to describe EC2 regions.", ex);
            } else if (regionsResp == null || regionsResp.regions().isEmpty()) {
                // Throw an exception if the response is null or the result is empty
                throw new RuntimeException("No EC2 regions found.");
            } else {
                // Process the response if no exception occurred and the result is not empty
                regionsResp.regions().forEach(region -> {
                    System.out.printf(
                        "Found Region %s with endpoint %s%n",
                        region.regionName(),
                        region.endpoint());
                });
            }
        });

        CompletableFuture<DescribeAvailabilityZonesResponse> zonesResponse = ec2AsyncClient.describeAvailabilityZones();
        CompletableFuture<DescribeAvailabilityZonesResponse> zonesFuture = zonesResponse.whenComplete((zonesResp, ex) -> {
            if (ex != null) {
                throw new RuntimeException("Failed to describe EC2 availability zones.", ex);
            } else if (zonesResp == null || zonesResp.availabilityZones().isEmpty()) {
                throw new RuntimeException("No EC2 availability zones found.");
            } else {
                zonesResp.availabilityZones().forEach(zone -> {
                    System.out.printf(
                        "Found Availability Zone %s with status %s in region %s%n",
                        zone.zoneName(),
                        zone.state(),
                        zone.regionName()
                    );
                });
            }
        });

        return CompletableFuture.allOf(regionsFuture, zonesFuture);
    }
}
```

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

## 가용 영역 설명
<a name="describe-availability-zones"></a>

계정에 사용할 수 있는 각 가용 영역을 나열하려면 Ec2Client의 `describeAvailabilityZones` 메서드를 호출합니다. 이 메서드는 [DescribeAvailabilityZonesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeAvailabilityZonesResponse.html)를 반환합니다. `availabilityZones` 메서드를 호출하여 각 가용 영역을 나타내는 [AvailabilityZone](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AvailabilityZone.html) 객체의 목록을 가져옵니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;
```

 **코드** 

Ec2Client를 생성하세요.

```
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();
```

그런 다음 describeAvailabilityZones()를 호출하고 결과를 검색합니다.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class DescribeRegionsAndZones {
    public static void main(String[] args) {
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();

        try {
            CompletableFuture<Void> future = describeEC2RegionsAndZonesAsync(ec2AsyncClient);
            future.join(); // Wait for both async operations to complete.
        } catch (RuntimeException rte) {
            System.err.println("An exception occurred: " + (rte.getCause() != null ? rte.getCause().getMessage() : rte.getMessage()));
        }
    }

    /**
     * Asynchronously describes the EC2 regions and availability zones.
     *
     * @param ec2AsyncClient the EC2 async client used to make the API calls
     * @return a {@link CompletableFuture} that completes when both the region and availability zone descriptions are complete
     */
    public static CompletableFuture<Void> describeEC2RegionsAndZonesAsync(Ec2AsyncClient ec2AsyncClient) {
        // Initiate the asynchronous request to describe regions
        CompletableFuture<DescribeRegionsResponse> regionsResponse = ec2AsyncClient.describeRegions();

        // Handle the response or exception for regions
        CompletableFuture<DescribeRegionsResponse> regionsFuture = regionsResponse.whenComplete((regionsResp, ex) -> {
            if (ex != null) {
                // Handle the exception by throwing a RuntimeException
                throw new RuntimeException("Failed to describe EC2 regions.", ex);
            } else if (regionsResp == null || regionsResp.regions().isEmpty()) {
                // Throw an exception if the response is null or the result is empty
                throw new RuntimeException("No EC2 regions found.");
            } else {
                // Process the response if no exception occurred and the result is not empty
                regionsResp.regions().forEach(region -> {
                    System.out.printf(
                        "Found Region %s with endpoint %s%n",
                        region.regionName(),
                        region.endpoint());
                });
            }
        });

        CompletableFuture<DescribeAvailabilityZonesResponse> zonesResponse = ec2AsyncClient.describeAvailabilityZones();
        CompletableFuture<DescribeAvailabilityZonesResponse> zonesFuture = zonesResponse.whenComplete((zonesResp, ex) -> {
            if (ex != null) {
                throw new RuntimeException("Failed to describe EC2 availability zones.", ex);
            } else if (zonesResp == null || zonesResp.availabilityZones().isEmpty()) {
                throw new RuntimeException("No EC2 availability zones found.");
            } else {
                zonesResp.availabilityZones().forEach(zone -> {
                    System.out.printf(
                        "Found Availability Zone %s with status %s in region %s%n",
                        zone.zoneName(),
                        zone.state(),
                        zone.regionName()
                    );
                });
            }
        });

        return CompletableFuture.allOf(regionsFuture, zonesFuture);
    }
}
```

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

## 계정 설명
<a name="describe-accounts"></a>

계정에 대한 EC2 관련 정보를 나열하려면 Ec2Client의 `describeAccountAttributes` 메서드를 호출하세요. 이 메서드는 [DescribeAccountAttributesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeAccountAttributesResponse.html) 객체를 반환합니다. 이 객체 `accountAttributes` 메서드를 호출하여 [AccountAttribute](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AccountAttribute.html) 객체 목록을 확보합니다. 목록 전체를 반복하여 `AccountAttribute` 객체를 검색할 수 있습니다.

`AccountAttribute` 객체의 `attributeValues` 메서드를 간접적으로 호출하여 계정의 속성 값을 확보할 수 있습니다. 이 메서드는 [AccountAttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AccountAttributeValue.html) 객체 목록을 반환합니다. 이 두 번째 목록을 반복하여 속성 값을 표시할 수 있습니다(다음 코드 예제 참조).

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeAccountAttributesResponse;
import java.util.concurrent.CompletableFuture;
```

 **코드** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeAccountAttributesResponse;
import java.util.concurrent.CompletableFuture;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class DescribeAccount {
    public static void main(String[] args) {
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();

        try {
            CompletableFuture<DescribeAccountAttributesResponse> future = describeEC2AccountAsync(ec2AsyncClient);
            future.join();
            System.out.println("EC2 Account attributes described successfully.");
        } catch (RuntimeException rte) {
            System.err.println("An exception occurred: " + (rte.getCause() != null ? rte.getCause().getMessage() : rte.getMessage()));
        }
    }

    /**
     * Describes the EC2 account attributes asynchronously.
     *
     * @param ec2AsyncClient the EC2 asynchronous client to use for the operation
     * @return a {@link CompletableFuture} containing the {@link DescribeAccountAttributesResponse} with the account attributes
     */
    public static CompletableFuture<DescribeAccountAttributesResponse> describeEC2AccountAsync(Ec2AsyncClient ec2AsyncClient) {
        CompletableFuture<DescribeAccountAttributesResponse> response = ec2AsyncClient.describeAccountAttributes();
        return response.whenComplete((accountResults, ex) -> {
            if (ex != null) {
                // Handle the exception by throwing a RuntimeException.
                throw new RuntimeException("Failed to describe EC2 account attributes.", ex);
            } else if (accountResults == null || accountResults.accountAttributes().isEmpty()) {
                // Throw an exception if the response is null or no account attributes are found.
                throw new RuntimeException("No account attributes found.");
            } else {
                // Process the response if no exception occurred.
                accountResults.accountAttributes().forEach(attribute -> {
                    System.out.println("\nThe name of the attribute is " + attribute.attributeName());
                    attribute.attributeValues().forEach(
                        myValue -> System.out.println("The value of the attribute is " + myValue.attributeValue()));
                });
            }
        });
    }
}
```

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

## 추가 정보
<a name="more-information"></a>
+  Linux 인스턴스용 Amazon EC2 사용 설명서의 [리전 및 가용 영역](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/using-regions-availability-zones.html) 
+  Amazon EC2 API 참조의 [DescribeRegions](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeRegions.html) 
+  Amazon EC2 API 참조의 [DescribeAvailabilityZones](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeAvailabilityZones.html) 

# Amazon EC2의 보안 그룹 작업
<a name="examples-ec2-security-groups"></a>

## 보안 그룹 생성
<a name="create-a-security-group"></a>

보안 그룹을 생성하려면 키 이름이 포함된 [CreateSecurityGroupRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/CreateSecurityGroupRequest.html)를 사용하여 Ec2Client의 `createSecurityGroup` 메서드를 호출하세요.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressResponse;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
import software.amazon.awssdk.services.ec2.model.IpPermission;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupResponse;
import software.amazon.awssdk.services.ec2.model.IpRange;
```

 ** 코드** 

```
            CreateSecurityGroupRequest createRequest = CreateSecurityGroupRequest.builder()
                .groupName(groupName)
                .description(groupDesc)
                .vpcId(vpcId)
                .build();

            CreateSecurityGroupResponse resp= ec2.createSecurityGroup(createRequest);
```

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

## 보안 그룹 구성
<a name="configure-a-security-group"></a>

보안 그룹은 Amazon EC2 인스턴스에 대한 인바운드(수신) 및 아웃바운드(송신) 트래픽을 모두 제어할 수 있습니다.

보안 그룹에 수신 규칙을 추가하려면 [AuthorizeSecurityGroupIngressRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AuthorizeSecurityGroupIngressRequest.html) 객체 내에 보안 그룹의 이름과 보안 그룹에 할당하려는 액세스 규칙([IpPermission](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/IpPermission.html))을 제공하여 Ec2Client의 `authorizeSecurityGroupIngress` 메서드를 사용합니다. 다음 예제에서는 IP 권한을 보안 그룹에 추가하는 방법을 보여줍니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressResponse;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
import software.amazon.awssdk.services.ec2.model.IpPermission;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupResponse;
import software.amazon.awssdk.services.ec2.model.IpRange;
```

 ** 코드** 

먼저 Ec2Client를 생성합니다.

```
        Region region = Region.US_WEST_2;
        Ec2Client ec2 = Ec2Client.builder()
                .region(region)
                .build();
```

그런 다음 Ec2Client의 `authorizeSecurityGroupIngress` 메서드를 사용하세요.

```
            IpRange ipRange = IpRange.builder()
                .cidrIp("0.0.0.0/0").build();

            IpPermission ipPerm = IpPermission.builder()
                .ipProtocol("tcp")
                .toPort(80)
                .fromPort(80)
                .ipRanges(ipRange)
                .build();

            IpPermission ipPerm2 = IpPermission.builder()
                .ipProtocol("tcp")
                .toPort(22)
                .fromPort(22)
                .ipRanges(ipRange)
                .build();

            AuthorizeSecurityGroupIngressRequest authRequest =
                AuthorizeSecurityGroupIngressRequest.builder()
                        .groupName(groupName)
                        .ipPermissions(ipPerm, ipPerm2)
                        .build();

            AuthorizeSecurityGroupIngressResponse authResponse =
            ec2.authorizeSecurityGroupIngress(authRequest);

            System.out.printf(
                "Successfully added ingress policy to Security Group %s",
                groupName);

            return resp.groupId();

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "";
    }
```

송신 규칙을 보안 그룹에 추가하려면 [AuthorizeSecurityGroupEgressRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AuthorizeSecurityGroupEgressRequest.html)의 유사한 데이터를 Ec2Client의 `authorizeSecurityGroupEgress` 메서드에 제공합니다.

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

## 보안 그룹 설명
<a name="describe-security-groups"></a>

보안 그룹을 설명하거나 보안 그룹에 대한 정보를 가져오려면 Ec2Client의 `describeSecurityGroups` 메서드를 호출합니다. 이 메서드는 [DescribeSecurityGroupsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeSecurityGroupsResponse.html)를 반환하는데, 이를 사용하여 `securityGroups` 메서드를 호출하여 보안 그룹 목록에 액세스할 수 있습니다. 그러면 [SecurityGroup](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/SecurityGroup.html) 객체 목록이 반환됩니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest;
import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsResponse;
import software.amazon.awssdk.services.ec2.model.SecurityGroup;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 ** 코드** 

```
     public static void describeEC2SecurityGroups(Ec2Client ec2, String groupId) {

        try {
            DescribeSecurityGroupsRequest request =
                DescribeSecurityGroupsRequest.builder()
                        .groupIds(groupId).build();

            DescribeSecurityGroupsResponse response =
                ec2.describeSecurityGroups(request);

             for(SecurityGroup group : response.securityGroups()) {
                System.out.printf(
                    "Found Security Group with id %s, " +
                            "vpc id %s " +
                            "and description %s",
                    group.groupId(),
                    group.vpcId(),
                    group.description());
            }
        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 보안 그룹 삭제
<a name="delete-a-security-group"></a>

보안 그룹을 삭제하려면 Ec2Client의 `deleteSecurityGroup` 메서드를 호출하여 삭제할 보안 그룹의 ID가 포함된 [DeleteSecurityGroupRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DeleteSecurityGroupRequest.html)를 이 메서드에 전달합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DeleteSecurityGroupRequest;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 ** 코드** 

```
    public static void deleteEC2SecGroup(Ec2Client ec2,String groupId) {

        try {
            DeleteSecurityGroupRequest request = DeleteSecurityGroupRequest.builder()
                .groupId(groupId)
                .build();

            ec2.deleteSecurityGroup(request);
            System.out.printf(
                "Successfully deleted Security Group with id %s", groupId);

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
     }
```

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

## 추가 정보
<a name="more-information"></a>
+  Amazon EC2 Linux 인스턴스용 사용 설명서의 [Amazon EC2 보안 그룹](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-security-groups.html)
+  Linux 인스턴스용 Amazon EC2 사용 설명서의 [Linux 인스턴스용 인바운드 트래픽 승인](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/authorizing-access-to-an-instance.html)
+  Amazon EC2 API 참조의 [CreateSecurityGroup](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateSecurityGroup.html)
+  Amazon EC2 API 참조의 [DescribeSecurityGroups](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
+  Amazon EC2 API 참조의 [DeleteSecurityGroup](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteSecurityGroup.html)
+  Amazon EC2 API 참조의 [AuthorizeSecurityGroupIngress](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_AuthorizeSecurityGroupIngress.html)

# Amazon EC2 인스턴스 메타데이터 작업
<a name="examples-ec2-IMDS"></a>

Amazon EC2 인스턴스 메타데이터 서비스(메타데이터 클라이언트)용 Java SDK 클라이언트를 사용하면 애플리케이션이 로컬 EC2 인스턴스의 메타데이터에 액세스할 수 있습니다. 메타데이터 클라이언트는 [IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html)(인스턴스 메타데이터 서비스 v2)의 로컬 인스턴스와 함께 작동하며 세션 지향 요청을 사용합니다.

SDK에서는 두 개의 클라이언트 클래스를 사용할 수 있습니다. 동기식은 `[Ec2MetadataClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/imds/Ec2MetadataClient.html)`는 작업 차단용이고 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/imds/Ec2MetadataAsyncClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/imds/Ec2MetadataAsyncClient.html)는 비동기식 비차단 사용 사례용입니다.

## 시작하기
<a name="examples-ec2-IMDS-getstarted"></a>

메타데이터 클라이언트를 사용하려면 `imds` Maven 아티팩트를 프로젝트에 추가하세요. 또한 클래스 경로에 `[SdkHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/SdkHttpClient.html)`(또는 비동기 변형용 `[SdkAsyncHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/async/SdkAsyncHttpClient.html)`)에 대한 클래스가 필요합니다.

다음 Maven XML은 메타데이터 클라이언트에 대한 종속성과 함께 동기 [UrlConnectionHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.html)를 사용하기 위한 종속성 코드 조각을 보여줍니다.

```
<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>imds</artifactId>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>url-connection-client</artifactId>
    </dependency>
    <!-- other dependencies --> 
</dependencies>
```

[Maven 중앙 리포지토리](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)에서 `bom` 아티팩트의 최신 버전을 검색하세요.

비동기 HTTP 클라이언트를 사용하려면 `url-connection-client` 아티팩트의 종속성 코드 조각을 바꾸세요. 예를 들어 다음 코드 조각은 [NettyNioAsyncHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.html) 구현을 가져옵니다.

```
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>netty-nio-client</artifactId>
    </dependency>
```

## 메타데이터 클라이언트를 사용
<a name="examples-ec2-IMDS-use"></a>

### 메타데이터 클라이언트 인스턴스화
<a name="examples-ec2-IMDS-use-create"></a>

클래스 경로에 `SdkHttpClient` 인터페이스 구현이 하나만 있는 경우 비동기 `Ec2MetadataClient`를 인스턴스화할 수 있습니다. 그러려면 다음 코드 조각과 같이 정적 `Ec2MetadataClient#create()` 메서드를 호출합니다.

```
Ec2MetadataClient client = Ec2MetadataClient.create(); // 'Ec2MetadataAsyncClient#create' is the asynchronous version.
```

애플리케이션에 `SdkHttpClient` 또는 `SdkHttpAsyncClient` 인터페이스가 여러 개 구현되어 있는 경우 [구성 가능한 HTTP 클라이언트](#examples-ec2-IMDS-features-http) 단원에 표시된 대로 메타데이터 클라이언트가 사용할 구현을 지정해야 합니다.

**참고**  
Amazon S3와 같은 대부분의 서비스 클라이언트의 경우 Java용 SDK는 `SdkHttpClient` 또는 `SdkHttpAsyncClient` 인터페이스의 구현을 자동으로 추가합니다. 메타데이터 클라이언트가 동일한 구현을 사용하는 경우 `Ec2MetadataClient#create()`가 작동합니다. 다른 구현이 필요한 경우 메타데이터 클라이언트를 만들 때 이를 지정해야 합니다.

### 요청 전송
<a name="examples-ec2-IMDS-use-req"></a>

인스턴스 메타데이터를 검색하려면 `EC2MetadataClient` 클래스를 인스턴스화하고 [인스턴스 메타데이터 카테고리](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html)를 지정하는 경로 파라미터를 사용하여 `get` 메서드를 호출합니다.

다음 예제는 `ami-id` 키와 관련된 값을 콘솔에 출력합니다.

```
Ec2MetadataClient client = Ec2MetadataClient.create();
Ec2MetadataResponse response = client.get("/latest/meta-data/ami-id");
System.out.println(response.asString());
client.close(); // Closes the internal resources used by the Ec2MetadataClient class.
```

경로가 유효하지 않은 경우 `get` 메서드에서 예외가 발생합니다.

여러 요청에 동일한 클라이언트 인스턴스를 재사용하되 리소스를 릴리스하는 데 더 이상 필요하지 않을 때는 클라이언트에서 `close`를 호출하세요. close 메서드가 호출된 후에는 클라이언트 인스턴스를 더 이상 사용할 수 없습니다.

### 응답 파싱
<a name="examples-ec2-IMDS-use-pares"></a>

EC2 인스턴스 메타데이터는 다양한 형식으로 출력될 수 있습니다. 일반 텍스트와 JSON이 가장 일반적으로 사용되는 형식입니다. 메타데이터 클라이언트는 이러한 형식을 사용할 수 있는 방법을 제공합니다.

다음 예제에서 볼 수 있듯이 `asString` 메서드를 사용하여 데이터를 Java 문자열로 가져옵니다. `asList` 메서드를 사용하여 여러 줄을 반환하는 일반 텍스트 응답을 분리할 수도 있습니다.

```
Ec2MetadataClient client = Ec2MetadataClient.create();
Ec2MetadataResponse response = client.get("/latest/meta-data/");
String fullResponse = response.asString();
List<String> splits = response.asList();
```

응답이 JSON인 경우 다음 코드 조각과 같이 `Ec2MetadataResponse#asDocument` 메서드를 사용하여 JSON 응답을 [Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/document/Document.html) 인스턴스로 파싱합니다.

```
Document fullResponse = response.asDocument();
```

메타데이터 형식이 JSON이 아닌 경우 예외가 발생합니다. 응답이 성공적으로 파싱되면 [문서 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/document/package-summary.html)를 사용하여 응답을 더 자세히 검사할 수 있습니다. 인스턴스 [메타데이터 카테고리 차트](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html)를 참조하여 JSON 형식의 응답을 제공하는 메타데이터 카테고리를 알아보세요.

## 메타데이터 클라이언트 구성
<a name="examples-ec2-IMDS-config"></a>

### 재시도
<a name="examples-ec2-IMDS-config-retries"></a>

재시도 메커니즘을 사용하여 메타데이터 클라이언트를 구성할 수 있습니다. 이렇게 하면 클라이언트가 예상치 못한 이유로 실패한 요청을 자동으로 재시도할 수 있습니다. 기본적으로 클라이언트는 시도 사이에 기하급수적인 백오프 시간을 두고 실패한 요청에 대해 세 번 재시도합니다.

사용 사례에 다른 재시도 메커니즘이 필요한 경우 빌더의 `retryPolicy` 메서드를 사용하여 클라이언트를 사용자 지정할 수 있습니다. 예를 들어 다음 예제는 시도 간 고정 지연 시간이 2초이고 재시도 횟수가 5회로 구성된 동기 클라이언트를 보여줍니다.

```
BackoffStrategy fixedBackoffStrategy = FixedDelayBackoffStrategy.create(Duration.ofSeconds(2));
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .retryPolicy(retryPolicyBuilder -> retryPolicyBuilder.numRetries(5)
                                                                           .backoffStrategy(fixedBackoffStrategy))
                     .build();
```

메타데이터 클라이언트에서 사용할 수 있는 여러 [BackoffStrategies](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/backoff/package-summary.html)가 있습니다.

다음 코드 조각과 같이 재시도 메커니즘을 완전히 비활성화할 수도 있습니다.

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                    .retryPolicy(Ec2MetadataRetryPolicy.none())
                    .build();
```

`Ec2MetadataRetryPolicy#none()`를 사용하면 기본 재시도 정책이 비활성화되어 메타데이터 클라이언트가 재시도를 시도하지 않습니다.

### IP 버전
<a name="examples-ec2-IMDS-config-ipversion"></a>

기본적으로 메타데이터 클라이언트는 `http://169.254.169.254`에서 IPV4 엔드포인트를 사용합니다. IPV6 버전을 사용하도록 클라이언트를 변경하려면 빌더의 `endpointMode` 또는 `endpoint` 메서드를 사용하세요. 빌더에서 두 메서드를 모두 호출하면 예외가 발생합니다.

다음 예제는 두 IPV6 옵션을 모두 보여줍니다.

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .endpointMode(EndpointMode.IPV6)
                     .build();
```

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .endpoint(URI.create("http://[fd00:ec2::254]"))
                     .build();
```

## 주요 기능
<a name="examples-ec2-IMDS-features"></a>

### 비동기 클라이언트
<a name="examples-ec2-IMDS-features-async"></a>

비차단 버전의 클라이언트를 사용하려면 `Ec2MetadataAsyncClient` 클래스의 인스턴스를 인스턴스화하세요. 다음 예제의 코드는 기본 설정으로 비동기 클라이언트를 만들고 `get` 메서드를 사용하여 `ami-id` 키 값을 검색합니다.

```
Ec2MetadataAsyncClient asyncClient = Ec2MetadataAsyncClient.create();
CompletableFuture<Ec2MetadataResponse> response = asyncClient.get("/latest/meta-data/ami-id");
```

`get` 메서드에서 반환된 `java.util.concurrent.CompletableFuture`은 응답이 반환될 때 완료됩니다. 다음 예제는 `ami-id` 메타데이터를 콘솔에 출력합니다.

```
response.thenAccept(metadata -> System.out.println(metadata.asString()));
```

### 구성 가능한 HTTP 클라이언트
<a name="examples-ec2-IMDS-features-http"></a>

각 메타데이터 클라이언트의 빌더에는 사용자 지정 HTTP 클라이언트를 제공하는 데 사용할 수 있는 `httpClient` 방법이 있습니다.

다음 예제는 사용자 지정 `UrlConnectionHttpClient` 인스턴스의 코드를 보여줍니다.

```
SdkHttpClient httpClient =
    UrlConnectionHttpClient.builder()
                           .socketTimeout(Duration.ofMinutes(5))
                           .proxyConfiguration(proxy -> proxy.endpoint(URI.create("http://proxy.example.net:8888"))))
                           .build();
Ec2MetadataClient metaDataClient =
    Ec2MetadataClient.builder()
                     .httpClient(httpClient)
                     .build();
// Use the metaDataClient instance.
metaDataClient.close();   // Close the instance when no longer needed.
```

다음 예제는 비동기 메타데이터 클라이언트가 있는 사용자 지정 `NettyNioAsyncHttpClient` 인스턴스의 코드를 보여줍니다.

```
SdkAsyncHttpClient httpAsyncClient = 
    NettyNioAsyncHttpClient.builder()
                           .connectionTimeout(Duration.ofMinutes(5))
                           .maxConcurrency(100)
                           .build();
Ec2MetadataAsyncClient asyncMetaDataClient =
    Ec2MetadataAsyncClient.builder()
                          .httpClient(httpAsyncClient)
                          .build();
// Use the asyncMetaDataClient instance.
asyncMetaDataClient.close();   // Close the instance when no longer needed.
```

이 가이드의 [에서 HTTP 클라이언트 구성 AWS SDK for Java 2.x](http-configuration.md) 항목에서는 Java용 SDK에서 사용할 수 있는 HTTP 클라이언트를 구성하는 방법에 대한 세부 정보를 제공합니다.

### 토큰 캐싱
<a name="examples-ec2-IMDS-features-token"></a>

메타데이터 클라이언트는 IMDSv2를 사용하므로 모든 요청은 세션과 연결됩니다. 세션은 메타데이터 클라이언트가 관리하는 만료일이 있는 토큰으로 정의됩니다. 모든 메타데이터 요청은 만료될 때까지 토큰을 자동으로 재사용합니다.

기본적으로 토큰은 6시간(21,600초) 동안 지속됩니다. 특정 사용 사례에 고급 구성이 필요한 경우가 아니면 기본 TTL(time to live) 값을 유지하는 것이 좋습니다.

필요한 경우 `tokenTtl` 빌더 메서드를 사용하여 기간을 구성하세요. 예를 들어 다음 코드 조각의 코드는 세션 기간이 5분인 클라이언트를 만듭니다.

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .tokenTtl(Duration.ofMinutes(5))
                     .build();
```

빌더에서 `tokenTtl` 메서드 호출을 생략하면 기본 기간인 21,600이 대신 사용됩니다.

# 작업 IAM
<a name="examples-iam"></a>

이 단원에서는 AWS SDK for Java 2.x를 사용한 프로그래밍 AWS Identity and Access Management (IAM)의 예를 제공합니다.

 AWS Identity and Access Management (IAM)를 사용하면 사용자의 AWS 서비스 및 리소스에 대한 액세스를 안전하게 제어할 수 있습니다. IAM를 사용하면 AWS 사용자 및 그룹을 생성 및 관리하고 권한을 사용하여 AWS 리소스에 대한 액세스를 허용 및 거부할 수 있습니다. 에 대한 전체 가이드는 [IAM 사용 설명서를](https://docs.aws.amazon.com//IAM/latest/UserGuide/introduction.html) IAM참조하세요.

다음 예제에는 각 기술을 보여주는 데 필요한 코드만 포함되어 있습니다. [전체 예제 코드는 GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)에 있습니다. 이 위치에서 단일 소스 파일을 다운로드하거나 리포지토리를 로컬로 복사하여 모든 예제를 빌드하고 실행할 수 있습니다.

**Topics**
+ [IAM 액세스 키 관리](examples-iam-access-keys.md)
+ [IAM 사용자 관리](examples-iam-users.md)
+ [IAM 정책 생성](feature-iam-policy-builder.md)
+ [IAM 정책 작업](examples-iam-policies.md)
+ [IAM 서버 인증서 작업](examples-iam-server-certificates.md)

# IAM 액세스 키 관리
<a name="examples-iam-access-keys"></a>

## 액세스 키 생성
<a name="create-an-access-key"></a>

IAM 액세스 키를 만들려면 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreateAccessKeyRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreateAccessKeyRequest.html) 객체를 사용하여 `IamClient’s` `createAccessKey` 메서드를 호출합니다.

**참고**  
IAM은 글로벌 서비스이므로 `IamClient` 호출이 작동하려면 리전을 **AWS\$1GLOBAL**로 설정해야 합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.services.iam.model.CreateAccessKeyRequest;
import software.amazon.awssdk.services.iam.model.CreateAccessKeyResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 ** 코드** 

```
    public static String createIAMAccessKey(IamClient iam,String user) {

        try {
            CreateAccessKeyRequest request = CreateAccessKeyRequest.builder()
                .userName(user).build();

            CreateAccessKeyResponse response = iam.createAccessKey(request);
           String keyId = response.accessKey().accessKeyId();
           return keyId;

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "";
    }
```

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

## 액세스 키 나열
<a name="list-access-keys"></a>

해당 사용자의 액세스 키를 나열하려면 키를 나열할 사용자 이름이 포함된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysRequest.html) 객체를 만들어 `IamClient’s` `listAccessKeys` 메서드에 전달합니다.

**참고**  
사용자 이름을 `listAccessKeys`에 제공하지 않으면 이 메서드는 요청에 서명한 AWS 계정과 연결된 액세스 키를 나열하려고 합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.services.iam.model.AccessKeyMetadata;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.ListAccessKeysRequest;
import software.amazon.awssdk.services.iam.model.ListAccessKeysResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 ** 코드** 

```
    public static void listKeys( IamClient iam,String userName ){

        try {
            boolean done = false;
            String newMarker = null;

            while (!done) {
                ListAccessKeysResponse response;

            if(newMarker == null) {
                ListAccessKeysRequest request = ListAccessKeysRequest.builder()
                        .userName(userName).build();
                response = iam.listAccessKeys(request);
            } else {
                ListAccessKeysRequest request = ListAccessKeysRequest.builder()
                        .userName(userName)
                        .marker(newMarker).build();
                response = iam.listAccessKeys(request);
            }

            for (AccessKeyMetadata metadata :
                    response.accessKeyMetadata()) {
                System.out.format("Retrieved access key %s",
                        metadata.accessKeyId());
            }

            if (!response.isTruncated()) {
                done = true;
            } else {
                newMarker = response.marker();
            }
        }

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

`listAccessKeys`의 결과가 페이징됩니다(호출당 기본 최대 100개 레코드). 반환된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysResponse.html) 객체에서 `isTruncated`를 호출하여 쿼리에서 반환된 결과 수가 사용 가능한 것보다 적은지 확인할 수 있습니다. 그런 후 `marker`에서 `ListAccessKeysResponse`를 호출해 새 요청 생성 때 사용합니다. 다음 `listAccessKeys` 호출에 이 새 요청을 사용합니다.

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

## 액세스 키의 마지막 사용 시간 가져오기
<a name="retrieve-an-access-key-s-last-used-time"></a>

액세스 키가 마지막으로 사용된 시간을 확인하려면 액세스 키의 ID([https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedRequest.html) 객체를 사용하여 전달할 수 있음)를 사용하여 `IamClient’s` `getAccessKeyLastUsed` 메서드를 호출합니다.

그런 다음 반환된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedResponse.html) 객체를 사용하여 키의 마지막 사용 시간을 검색할 수 있습니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.GetAccessKeyLastUsedRequest;
import software.amazon.awssdk.services.iam.model.GetAccessKeyLastUsedResponse;
import software.amazon.awssdk.services.iam.model.IamException;
```

 ** 코드** 

```
    public static void getAccessKeyLastUsed(IamClient iam, String accessId ){

        try {
            GetAccessKeyLastUsedRequest request = GetAccessKeyLastUsedRequest.builder()
                    .accessKeyId(accessId).build();

            GetAccessKeyLastUsedResponse response = iam.getAccessKeyLastUsed(request);

            System.out.println("Access key was last used at: " +
                    response.accessKeyLastUsed().lastUsedDate());

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        System.out.println("Done");
    }
```

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

## 액세스 키 활성화 또는 비활성화
<a name="iam-access-keys-update"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateAccessKeyRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateAccessKeyRequest.html) 객체를 만들고 액세스 키 ID, 사용자 이름(선택 사항) 및 원하는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/StatusType.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/StatusType.html)를 제공한 후, 요청 객체를 `IamClient’s` `updateAccessKey` 메서드로 전달하여 액세스 키를 활성화하거나 비활성화할 수 있습니다.

 **가져옵니다**.

```
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.StatusType;
import software.amazon.awssdk.services.iam.model.UpdateAccessKeyRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 ** 코드** 

```
       public static void updateKey(IamClient iam, String username, String accessId, String status ) {

          try {
              if (status.toLowerCase().equalsIgnoreCase("active")) {
                  statusType = StatusType.ACTIVE;
              } else if (status.toLowerCase().equalsIgnoreCase("inactive")) {
                  statusType = StatusType.INACTIVE;
              } else {
                  statusType = StatusType.UNKNOWN_TO_SDK_VERSION;
              }
              UpdateAccessKeyRequest request = UpdateAccessKeyRequest.builder()
                .accessKeyId(accessId)
                .userName(username)
                .status(statusType)
                .build();

              iam.updateAccessKey(request);

              System.out.printf(
                "Successfully updated the status of access key %s to" +
                        "status %s for user %s", accessId, status, username);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 액세스 키 삭제
<a name="delete-an-access-key"></a>

액세스 키를 영구적으로 삭제하려면 `IamClient’s` `deleteKey` 메서드를 호출하여 액세스 키의 ID와 사용자 이름이 포함된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DeleteAccessKeyRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DeleteAccessKeyRequest.html)를 이 메서드에 제공합니다.

**참고**  
키는 삭제하고 나면 더 이상 가져오거나 사용할 수 없습니다. 나중에 다시 활성화할 수 있도록 키를 일시적으로 비활성화하려면 대신에 [`updateAccessKey`](#iam-access-keys-update) 메서드를 사용합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.DeleteAccessKeyRequest;
import software.amazon.awssdk.services.iam.model.IamException;
```

 ** 코드** 

```
    public static void deleteKey(IamClient iam ,String username, String accessKey ) {

        try {
            DeleteAccessKeyRequest request = DeleteAccessKeyRequest.builder()
                    .accessKeyId(accessKey)
                    .userName(username)
                    .build();

            iam.deleteAccessKey(request);
            System.out.println("Successfully deleted access key " + accessKey +
                " from user " + username);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 추가 정보
<a name="more-information"></a>
+  IAM API 참조에서 [CreateAccessKey](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateAccessKey.html)
+  IAM API 참조에서 [ListAccessKeys](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAccessKeys.html)
+  IAM API 참조에서 [GetAccessKeyLastUsed](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetAccessKeyLastUsed.html)
+  IAM API 참조에서 [UpdateAccessKey](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateAccessKey.html)
+  IAM API 참조에서 [DeleteAccessKey](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteAccessKey.html)

# IAM 사용자 관리
<a name="examples-iam-users"></a>

## 사용자 생성
<a name="creating-a-user"></a>

사용자 이름이 포함된 [CreateUserRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreateUserRequest.html) 객체를 사용하여 IamClient의 `createUser` 메서드에 사용자 이름을 제공하여 새 IAM 사용자를 생성합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.iam.model.CreateUserRequest;
import software.amazon.awssdk.services.iam.model.CreateUserResponse;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.waiters.IamWaiter;
import software.amazon.awssdk.services.iam.model.GetUserRequest;
import software.amazon.awssdk.services.iam.model.GetUserResponse;
```

 ** 코드** 

```
    public static String createIAMUser(IamClient iam, String username ) {

        try {
            // Create an IamWaiter object
            IamWaiter iamWaiter = iam.waiter();

            CreateUserRequest request = CreateUserRequest.builder()
                    .userName(username)
                    .build();

            CreateUserResponse response = iam.createUser(request);

            // Wait until the user is created
            GetUserRequest userRequest = GetUserRequest.builder()
                    .userName(response.user().userName())
                    .build();

            WaiterResponse<GetUserResponse> waitUntilUserExists = iamWaiter.waitUntilUserExists(userRequest);
            waitUntilUserExists.matched().response().ifPresent(System.out::println);
            return response.user().userName();

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
       return "";
    }
```

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

## 사용자 나열
<a name="listing-users"></a>

계정의 IAM 사용자를 나열하려면 새 [ListUsersRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListUsersRequest.html)를 생성하여 IamClient의 `listUsers` 메서드에 전달합니다. 반환된 [ListUsersResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListUsersResponse.html) 객체의 `users`를 호출하여 사용자 목록을 검색할 수 있습니다.

`listUsers`에서 반환된 사용자 목록이 페이징됩니다. 응답 객체의 `isTruncated` 메서드를 호출하여 가져올 결과가 더 있는지 확인할 수 있습니다. 그런 다음 `true`를 반환하면 응답 객체의 `marker()` 메서드를 호출합니다. 마커 값을 사용해 새 요청 객체를 생성합니다. 그런 다음 새 요청을 사용해 다시 `listUsers` 메서드를 호출합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.ListUsersRequest;
import software.amazon.awssdk.services.iam.model.ListUsersResponse;
import software.amazon.awssdk.services.iam.model.User;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 ** 코드** 

```
    public static void listAllUsers(IamClient iam ) {

        try {

             boolean done = false;
             String newMarker = null;

             while(!done) {
                ListUsersResponse response;

                if (newMarker == null) {
                    ListUsersRequest request = ListUsersRequest.builder().build();
                    response = iam.listUsers(request);
                } else {
                    ListUsersRequest request = ListUsersRequest.builder()
                        .marker(newMarker).build();
                    response = iam.listUsers(request);
                }

                for(User user : response.users()) {
                 System.out.format("\n Retrieved user %s", user.userName());
                }

                if(!response.isTruncated()) {
                  done = true;
                } else {
                    newMarker = response.marker();
                }
            }
        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 사용자 업데이트
<a name="updating-a-user"></a>

사용자를 업데이트하려면 IamClient 객체의 `updateUser` 메서드를 호출합니다. 이 메서드는 사용자 *이름* 또는 *경로*를 변경하는 데 사용할 수 있는 [UpdateUserRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateUserRequest.html) 객체를 사용합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.UpdateUserRequest;
```

 ** 코드** 

```
    public static void updateIAMUser(IamClient iam, String curName,String newName ) {

        try {
            UpdateUserRequest request = UpdateUserRequest.builder()
                    .userName(curName)
                    .newUserName(newName)
                    .build();

            iam.updateUser(request);
            System.out.printf("Successfully updated user to username %s",
                newName);
        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
      }
```

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

## 사용자 삭제
<a name="deleting-a-user"></a>

사용자를 삭제하려면 삭제할 사용자 이름으로 설정된 [UpdateUserRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateUserRequest.html) 객체를 사용하여 IAMClient의 `deleteUser` 요청을 호출합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.DeleteUserRequest;
import software.amazon.awssdk.services.iam.model.IamException;
```

 ** 코드** 

```
    public static void deleteIAMUser(IamClient iam, String userName) {

        try {
            DeleteUserRequest request = DeleteUserRequest.builder()
                    .userName(userName)
                    .build();

            iam.deleteUser(request);
            System.out.println("Successfully deleted IAM user " + userName);
        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 추가 정보
<a name="more-information"></a>
+  IAM 사용 설명서의 [IAM 사용자](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html)
+  IAM 사용 설명서의 [IAM 사용자 관리](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_manage.html)
+  IAM API 참조의 [CreateUser](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateUser.html)
+  IAM API 참조의 [ListUsers](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListUsers.html)
+  IAM API 참조의 [UpdateUser](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateUser.html)
+  IAM API 참조의 [DeleteUser](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteUser.html)

# 를 사용하여 IAM 정책 생성 AWS SDK for Java 2.x
<a name="feature-iam-policy-builder"></a>

[IAM 정책 빌더 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/package-summary.html)는 Java에서 [IAM 정책을](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html) 빌드하고 AWS Identity and Access Management (IAM)에 업로드하는 데 사용할 수 있는 라이브러리입니다.

JSON 문자열을 수동으로 조합하거나 파일을 읽어 IAM 정책을 구축하는 대신 API는 JSON 문자열을 생성하기 위한 클라이언트 측 객체 지향 접근 방식을 제공합니다. JSON 형식의 기존 IAM 정책을 읽으면 API가 이를 [IamPolicy](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/IamPolicy.html) 인스턴스로 변환하여 처리합니다.

IAM 정책 빌더 API는 SDK 버전 2.20.105에서 사용할 수 있으므로 Maven 빌드 파일에서 해당 버전 또는 이후 버전을 사용하세요. SDK의 최신 버전 번호는 [Maven 센트럴에 나와 있습니다](https://central.sonatype.com/artifact/software.amazon.awssdk/iam-policy-builder).

다음 코드 조각은 Maven `pom.xml` 파일의 종속성 블록 예제를 보여줍니다. 이를 통해 프로젝트에서 IAM 정책 빌더 API를 사용할 수 있습니다.

```
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>iam-policy-builder</artifactId>
    <version>2.27.21</version>
</dependency>
```

## `IamPolicy` 생성
<a name="iam-policy-builder-create"></a>

이 단원에서는 IAM Policy Builder API를 사용하여 정책을 구축하는 방법에 대한 몇 가지 예제를 보여줍니다.

다음 각 예제에서 `[IamPolicy.Builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/IamPolicy.Builder.html)`로 시작하여 `addStatement` 메서드를 사용하여 명령문을 하나 이상 추가합니다. 이 패턴에 따라 [IamStatement.Builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/IamStatement.Builder.html)에는 명령문에 효과, 액션, 리소스 및 조건을 추가하는 메서드가 있습니다.

### 예제: 시간 기반 정책을 생성
<a name="iam-policy-builder-create-ex-time-based"></a>

다음 예제는 두 시점 사이에 Amazon DynamoDB`GetItem` 작업을 허용하는 자격 증명 기반 정책을 생성합니다.

```
    public String timeBasedPolicyExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.ALLOW)
                        .addAction("dynamodb:GetItem")
                        .addResource(IamResource.ALL)
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.DATE_GREATER_THAN)
                                .key("aws:CurrentTime")
                                .value("2020-04-01T00:00:00Z"))
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.DATE_LESS_THAN)
                                .key("aws:CurrentTime")
                                .value("2020-06-30T23:59:59Z")))
                .build();

        // Use an IamPolicyWriter to write out the JSON string to a more readable format.
        return policy.toJson(IamPolicyWriter.builder()
                        .prettyPrint(true)
                        .build());
    }
```

#### JSON 출력
<a name="iam-builder-ex-json-date"></a>

이전 예제의 마지막 문은 다음 JSON 문자열을 반환합니다.

이 [예제](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws-dates.html)에 대한 자세한 내용은 *AWS Identity and Access Management 사용 설명서*를 참조하세요.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": {
        "Effect": "Allow",
        "Action": "dynamodb:GetItem",
        "Resource": "*",
        "Condition": {
            "DateGreaterThan": {
                "aws:CurrentTime": "2020-04-01T00:00:00Z"
            },
            "DateLessThan": {
                "aws:CurrentTime": "2020-06-30T23:59:59Z"
            }
        }
    }
}
```

------

### 예제: 여러 조건 지정
<a name="iam-policy-builder-create-ex-multi-conditions"></a>

이 예제는 특정 DynamoDB 속성에 대한 액세스를 허용하는 아이덴티티 기반 정책을 생성하는 방법을 보여줍니다. 정책에는 두 가지 조건이 있습니다.

```
    public String multipleConditionsExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.ALLOW)
                        .addAction("dynamodb:GetItem")
                        .addAction("dynamodb:BatchGetItem")
                        .addAction("dynamodb:Query")
                        .addAction("dynamodb:PutItem")
                        .addAction("dynamodb:UpdateItem")
                        .addAction("dynamodb:DeleteItem")
                        .addAction("dynamodb:BatchWriteItem")
                        .addResource("arn:aws:dynamodb:*:*:table/table-name")
                        .addConditions(IamConditionOperator.STRING_EQUALS.addPrefix("ForAllValues:"),
                                "dynamodb:Attributes",
                                List.of("column-name1", "column-name2", "column-name3"))
                        .addCondition(b1 -> b1.operator(IamConditionOperator.STRING_EQUALS.addSuffix("IfExists"))
                                .key("dynamodb:Select")
                                .value("SPECIFIC_ATTRIBUTES")))
                .build();

        return policy.toJson(IamPolicyWriter.builder()
                .prettyPrint(true).build());
    }
```

#### JSON 출력
<a name="iam-builder-ex-json-multi-cond"></a>

이전 예제의 마지막 문은 다음 JSON 문자열을 반환합니다.

이 [예제](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_attributes.html)에 대한 자세한 내용은 *AWS Identity and Access Management 사용 설명서*를 참조하세요.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": {
        "Effect": "Allow",
        "Action": [
            "dynamodb:GetItem",
            "dynamodb:BatchGetItem",
            "dynamodb:Query",
            "dynamodb:PutItem",
            "dynamodb:UpdateItem",
            "dynamodb:DeleteItem",
            "dynamodb:BatchWriteItem"
        ],
        "Resource": "arn:aws:dynamodb:*:*:table/table-name",
        "Condition": {
            "ForAllValues:StringEquals": {
                "dynamodb:Attributes": [
                    "column-name1",
                    "column-name2",
                    "column-name3"
                ]
            },
            "StringEqualsIfExists": {
                "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
            }
        }
    }
}
```

------

### 예제: 주체 지정
<a name="iam-policy-builder-create-ex-principals"></a>

다음 예제는 조건에 지정된 주체를 제외한 모든 주체의 버킷 액세스를 거부하는 리소스 기반 정책을 생성하는 방법을 보여줍니다.

```
    public String specifyPrincipalsExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.DENY)
                        .addAction("s3:*")
                        .addPrincipal(IamPrincipal.ALL)
                        .addResource("arn:aws:s3:::BUCKETNAME/*")
                        .addResource("arn:aws:s3:::BUCKETNAME")
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.ARN_NOT_EQUALS)
                                .key("aws:PrincipalArn")
                                .value("arn:aws:iam::444455556666:user/user-name")))
                .build();
        return policy.toJson(IamPolicyWriter.builder()
                .prettyPrint(true).build());
    }
```

#### JSON 출력
<a name="iam-policy-builder-create-json-ex-principals"></a>

이전 예제의 마지막 문은 다음 JSON 문자열을 반환합니다.

이 [예제](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html#principal-anonymous)에 대한 자세한 내용은 *AWS Identity and Access Management 사용 설명서*를 참조하세요.

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Deny",
    "Principal" : "*",
    "Action" : "s3:*",
    "Resource" : [ "arn:aws:s3:::BUCKETNAME/*", "arn:aws:s3:::BUCKETNAME" ],
    "Condition" : {
      "ArnNotEquals" : {
        "aws:PrincipalArn" : "arn:aws:iam::444455556666:user/user-name"
      }
    }
  }
}
```

------

### 예제: 교차 계정 액세스를 허용
<a name="iam-policy-builder-create-ex-cross-account"></a>

다음 예제에서는 업로드된 객체에 대한 전체 소유자 제어를 유지하면서 다른이 버킷에 객체를 업로드 AWS 계정 하도록 허용하는 방법을 보여줍니다.

```
    public String allowCrossAccountAccessExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.ALLOW)
                        .addPrincipal(IamPrincipalType.AWS, "111122223333")
                        .addAction("s3:PutObject")
                        .addResource("arn:aws:s3:::amzn-s3-demo-bucket/*")
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.STRING_EQUALS)
                                .key("s3:x-amz-acl")
                                .value("bucket-owner-full-control")))
                .build();
        return policy.toJson(IamPolicyWriter.builder()
                .prettyPrint(true).build());
    }
```

#### JSON 출력
<a name="iam-policy-builder-create-ex-json-cross-account"></a>

이전 예제의 마지막 문은 다음 JSON 문자열을 반환합니다.

이 [예제](https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html#example-bucket-policies-acl-2)에 대한 자세한 내용은 *Amazon Simple Storage Service 사용 설명서*를 참조하세요.

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Allow",
    "Principal" : {
      "AWS" : "111122223333"
    },
    "Action" : "s3:PutObject",
    "Resource" : "arn:aws:s3:::amzn-s3-demo-bucket/*",
    "Condition" : {
      "StringEquals" : {
        "s3:x-amz-acl" : "bucket-owner-full-control"
      }
    }
  }
}
```

------

## IAM과 함께 `IamPolicy` 사용
<a name="iam-policy-builder-work-with-service"></a>

`IamPolicy` 인스턴스를 생성한 후에는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/IamClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/IamClient.html)를 사용하여 IAM 서비스를 사용할 수 있습니다.

다음 예제는 [IAM ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/id.html)가 `accountID` 파라미터로 지정된 계정의 DynamoDB 테이블에 항목을 쓸 수 있도록 허용하는 정책을 작성합니다. 그러면 정책이 IAM에 JSON 문자열로 업로드됩니다.

```
    public String createAndUploadPolicyExample(IamClient iam, String accountID, String policyName) {
        // Build the policy.
        IamPolicy policy =
                IamPolicy.builder() // 'version' defaults to "2012-10-17".
                        .addStatement(IamStatement.builder()
                                .effect(IamEffect.ALLOW)
                                .addAction("dynamodb:PutItem")
                                .addResource("arn:aws:dynamodb:us-east-1:" + accountID + ":table/exampleTableName")
                                .build())
                        .build();
        // Upload the policy.
        iam.createPolicy(r -> r.policyName(policyName).policyDocument(policy.toJson()));
        return policy.toJson(IamPolicyWriter.builder().prettyPrint(true).build());
    }
```

이 예제는 이전 예제를 기반으로 구축되었습니다. 코드는 정책을 다운로드하고 명령문을 복사하고 변경하여 이를 새 정책의 기초로 사용합니다. 그러면 새 정책이 업로드됩니다.

```
    public String createNewBasedOnExistingPolicyExample(IamClient iam, String accountID, String policyName, String newPolicyName) {

        String policyArn = "arn:aws:iam::" + accountID + ":policy/" + policyName;
        GetPolicyResponse getPolicyResponse = iam.getPolicy(r -> r.policyArn(policyArn));

        String policyVersion = getPolicyResponse.policy().defaultVersionId();
        GetPolicyVersionResponse getPolicyVersionResponse =
                iam.getPolicyVersion(r -> r.policyArn(policyArn).versionId(policyVersion));

        // Create an IamPolicy instance from the JSON string returned from IAM.
        String decodedPolicy = URLDecoder.decode(getPolicyVersionResponse.policyVersion().document(), StandardCharsets.UTF_8);
        IamPolicy policy = IamPolicy.fromJson(decodedPolicy);

            /*
             All IamPolicy components are immutable, so use the copy method that creates a new instance that
             can be altered in the same method call.

             Add the ability to get an item from DynamoDB as an additional action.
            */
        IamStatement newStatement = policy.statements().get(0).copy(s -> s.addAction("dynamodb:GetItem"));

        // Create a new statement that replaces the original statement.
        IamPolicy newPolicy = policy.copy(p -> p.statements(Arrays.asList(newStatement)));

        // Upload the new policy. IAM now has both policies.
        iam.createPolicy(r -> r.policyName(newPolicyName)
                .policyDocument(newPolicy.toJson()));

        return newPolicy.toJson(IamPolicyWriter.builder().prettyPrint(true).build());
    }
```

### IamClient
<a name="iam-policy-builder-work-with-serivce-create-client"></a>

이전 예제는 다음 코드 조각에 표시된 것처럼 생성된 `IamClient` 인수를 사용합니다.

```
IamClient iam = IamClient.builder().region(Region.AWS_GLOBAL).build();
```

### JSON 형식의 정책
<a name="iam-policy-builder-work-with-serivce-json"></a>

예제에서는 다음 JSON 문자열을 반환합니다.

```
First example
{
  "Version": "2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Allow",
    "Action" : "dynamodb:PutItem",
    "Resource" : "arn:aws:dynamodb:us-east-1:111122223333:table/exampleTableName"
  }
}

Second example
{
  "Version": "2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Allow",
    "Action" : [ "dynamodb:PutItem", "dynamodb:GetItem" ],
    "Resource" : "arn:aws:dynamodb:us-east-1:111122223333:table/exampleTableName"
  }
}
```

# IAM 정책 작업
<a name="examples-iam-policies"></a>

## 정책 생성
<a name="create-a-policy"></a>

새 정책을 생성하려면 [CreatePolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreatePolicyRequest.html)의 정책 이름과 JSON 형식으로 된 정책 문서를 IamClient의 `createPolicy` 메서드에 제공합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.iam.model.CreatePolicyRequest;
import software.amazon.awssdk.services.iam.model.CreatePolicyResponse;
import software.amazon.awssdk.services.iam.model.GetPolicyRequest;
import software.amazon.awssdk.services.iam.model.GetPolicyResponse;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.waiters.IamWaiter;
```

 ** 코드** 

```
    public static String createIAMPolicy(IamClient iam, String policyName ) {

        try {
            // Create an IamWaiter object
            IamWaiter iamWaiter = iam.waiter();

            CreatePolicyRequest request = CreatePolicyRequest.builder()
                .policyName(policyName)
                .policyDocument(PolicyDocument).build();

            CreatePolicyResponse response = iam.createPolicy(request);

            // Wait until the policy is created
            GetPolicyRequest polRequest = GetPolicyRequest.builder()
                    .policyArn(response.policy().arn())
                    .build();

            WaiterResponse<GetPolicyResponse> waitUntilPolicyExists = iamWaiter.waitUntilPolicyExists(polRequest);
            waitUntilPolicyExists.matched().response().ifPresent(System.out::println);
            return response.policy().arn();

         } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "" ;
    }
```

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

## 정책 가져오기
<a name="get-a-policy"></a>

기존 정책을 검색하려면 [GetPolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetPolicyRequest.html) 객체 내에 정책의 ARN을 제공하여 IamClient의 `getPolicy` 메서드를 호출하세요.

 **가져옵니다**.

```
import software.amazon.awssdk.services.iam.model.GetPolicyRequest;
import software.amazon.awssdk.services.iam.model.GetPolicyResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 ** 코드** 

```
    public static void getIAMPolicy(IamClient iam, String policyArn) {

        try {
            GetPolicyRequest request = GetPolicyRequest.builder()
                .policyArn(policyArn).build();

            GetPolicyResponse response = iam.getPolicy(request);
            System.out.format("Successfully retrieved policy %s",
                response.policy().policyName());

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 역할 정책 연결
<a name="attach-a-role-policy"></a>

IAMClient의 `attachRolePolicy` 메서드를 호출하고 [AttachrolePolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/AttachRolePolicyRequest.html)에 역할 이름 및 정책 ARN을 제공하여 정책을 IAM [역할](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html)에 연결할 수 있습니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.AttachRolePolicyRequest;
import software.amazon.awssdk.services.iam.model.AttachedPolicy;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesRequest;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesResponse;
import java.util.List;
```

 ** 코드** 

```
    public static void attachIAMRolePolicy(IamClient iam, String roleName, String policyArn ) {

        try {

             ListAttachedRolePoliciesRequest request = ListAttachedRolePoliciesRequest.builder()
                    .roleName(roleName)
                    .build();

            ListAttachedRolePoliciesResponse  response = iam.listAttachedRolePolicies(request);
            List<AttachedPolicy> attachedPolicies = response.attachedPolicies();

            // Ensure that the policy is not attached to this role
            String polArn = "";
            for (AttachedPolicy policy: attachedPolicies) {
                polArn = policy.policyArn();
                if (polArn.compareTo(policyArn)==0) {
                   System.out.println(roleName +
                            " policy is already attached to this role.");
                    return;
                }
          }

            AttachRolePolicyRequest attachRequest =
                AttachRolePolicyRequest.builder()
                        .roleName(roleName)
                        .policyArn(policyArn)
                        .build();

            iam.attachRolePolicy(attachRequest);

            System.out.println("Successfully attached policy " + policyArn +
                " to role " + roleName);

         } catch (IamException e) {
                System.err.println(e.awsErrorDetails().errorMessage());
                System.exit(1);
          }

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

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

## 연결된 역할 정책 나열
<a name="list-attached-role-policies"></a>

IamClient의 `listAttachedRolePolicies` 메서드를 호출하여 역할의 연결된 정책을 나열합니다. 이 메서드는 정책을 나열할 역할 이름을 포함하는 [ListAttachedRolePoliciesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAttachedRolePoliciesRequest.html) 객체를 사용합니다.

반환된 [ListAttachedRolePoliciesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAttachedRolePoliciesResponse.html) 객체에 `getAttachedPolicies`를 호출하여 연결된 정책 목록을 가져옵니다. `ListAttachedRolePoliciesResponse` 객체의 `isTruncated` 메서드가 `true`를 반환하고, `ListAttachedRolePoliciesResponse` 객체의 `marker` 메서드를 호출하는 경우 결과가 잘릴 수 있습니다. 반환된 마커를 사용하여 새 요청을 생성하고, 이를 사용하여 `listAttachedRolePolicies`를 다시 호출해 다음 검색 배치를 가져옵니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.AttachRolePolicyRequest;
import software.amazon.awssdk.services.iam.model.AttachedPolicy;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesRequest;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesResponse;
import java.util.List;
```

 ** 코드** 

```
    public static void attachIAMRolePolicy(IamClient iam, String roleName, String policyArn ) {

        try {

             ListAttachedRolePoliciesRequest request = ListAttachedRolePoliciesRequest.builder()
                    .roleName(roleName)
                    .build();

            ListAttachedRolePoliciesResponse  response = iam.listAttachedRolePolicies(request);
            List<AttachedPolicy> attachedPolicies = response.attachedPolicies();

            // Ensure that the policy is not attached to this role
            String polArn = "";
            for (AttachedPolicy policy: attachedPolicies) {
                polArn = policy.policyArn();
                if (polArn.compareTo(policyArn)==0) {
                   System.out.println(roleName +
                            " policy is already attached to this role.");
                    return;
                }
          }

            AttachRolePolicyRequest attachRequest =
                AttachRolePolicyRequest.builder()
                        .roleName(roleName)
                        .policyArn(policyArn)
                        .build();

            iam.attachRolePolicy(attachRequest);

            System.out.println("Successfully attached policy " + policyArn +
                " to role " + roleName);

         } catch (IamException e) {
                System.err.println(e.awsErrorDetails().errorMessage());
                System.exit(1);
          }

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

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

## 역할 정책 분리
<a name="detach-a-role-policy"></a>

역할에서 정책을 분리하려면 IamClient의 `detachRolePolicy` 메서드를 호출하여 [DetachRolePolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DetachRolePolicyRequest.html)의 역할 이름과 정책 ARN을 이 메서드에 지정합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.services.iam.model.DetachRolePolicyRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 ** 코드** 

```
    public static void detachPolicy(IamClient iam, String roleName, String policyArn ) {

        try {
            DetachRolePolicyRequest request = DetachRolePolicyRequest.builder()
                    .roleName(roleName)
                    .policyArn(policyArn)
                    .build();

            iam.detachRolePolicy(request);
            System.out.println("Successfully detached policy " + policyArn +
                " from role " + roleName);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 추가 정보
<a name="more-information"></a>
+  IAM 사용 설명서의 [IAM 정책 개요](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html).
+ IAM 사용 설명서의 [AWS IAM 정책 참조](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html).
+  IAM API 참조의 [CreatePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreatePolicy.html)
+  IAM API 참조의 [GetPolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetPolicy.html)
+  IAM API 참조의 [AttachRolePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_AttachRolePolicy.html)
+  IAM API 참조의 [ListAttachedRolePolicies](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAttachedRolePolicies.html)
+  IAM API 참조의 [DetachRolePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DetachRolePolicy.html)

# IAM 서버 인증서 작업
<a name="examples-iam-server-certificates"></a>

에서 웹 사이트 또는 애플리케이션에 대한 HTTPS 연결을 활성화하려면 SSL/TLS *서버 인증서*가 AWS필요합니다. 에서 제공하는 서버 인증서 AWS Certificate Manager 또는 외부 공급자로부터 받은 서버 인증서를 사용할 수 있습니다.

 ACM 를 사용하여 서버 인증서를 프로비저닝, 관리 및 배포하는 것이 좋습니다. 를 ACM 사용하면 인증서를 요청하여 AWS 리소스에 배포하고가 인증서 갱신을 ACM 처리하도록 할 수 있습니다. 에서 제공하는 인증서 ACM 는 무료입니다. 에 대한 자세한 내용은 [AWS Certificate Manager 사용 설명서를](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) ACM참조하세요.

## 서버 인증서 조회
<a name="get-a-server-certificate"></a>

IamClient의 `getServerCertificate` 메서드를 호출하고 인증서 이름이 포함된 [GetServerCertificateRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetServerCertificateRequest.html)를 이 메서드에 전달하여 서버 인증서를 검색할 수 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.services.iam.model.GetServerCertificateRequest;
import software.amazon.awssdk.services.iam.model.GetServerCertificateResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **코드** 

```
    public static void getCertificate(IamClient iam,String certName ) {

        try {
            GetServerCertificateRequest request = GetServerCertificateRequest.builder()
                    .serverCertificateName(certName)
                    .build();

            GetServerCertificateResponse response = iam.getServerCertificate(request);
            System.out.format("Successfully retrieved certificate with body %s",
                response.serverCertificate().certificateBody());

         } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 서버 인증서 나열
<a name="list-server-certificates"></a>

서버 인증서를 나열하려면 [ListServerCertificatesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListServerCertificatesRequest.html)를 사용하여 IamClient의 `listServerCertificates` 메서드를 호출하세요. 그러면 [ListServerCertificatesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListServerCertificatesResponse.html)가 반환됩니다.

반환된 `ListServerCertificateResponse` 객체의 `serverCertificateMetadataList` 메서드를 호출하여 각 인증서에 대한 정보를 가져오는 데 사용할 수 있는 [ServerCertificateMetadata](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ServerCertificateMetadata.html) 객체의 목록을 가져옵니다.

`ListServerCertificateResponse` 객체의 `isTruncated` 메서드가 `true`을 반환하고, `ListServerCertificatesResponse` 객체의 `marker` 메서드를 호출하고, 마커를 사용하여 새 요청을 생성하는 경우 결과가 잘릴 수도 있습니다. 이 경우 새 요청을 사용하여 `listServerCertificates`를 다시 호출해 다음 결과들을 가져옵니다.

 **가져오기** 

```
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.ListServerCertificatesRequest;
import software.amazon.awssdk.services.iam.model.ListServerCertificatesResponse;
import software.amazon.awssdk.services.iam.model.ServerCertificateMetadata;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 **코드** 

```
    public static void listCertificates(IamClient iam) {

        try {
            boolean done = false;
            String newMarker = null;

            while(!done) {
              ListServerCertificatesResponse response;

            if (newMarker == null) {
                ListServerCertificatesRequest request =
                        ListServerCertificatesRequest.builder().build();
                response = iam.listServerCertificates(request);
            } else {
                ListServerCertificatesRequest request =
                        ListServerCertificatesRequest.builder()
                                .marker(newMarker).build();
                response = iam.listServerCertificates(request);
            }

            for(ServerCertificateMetadata metadata :
                    response.serverCertificateMetadataList()) {
                System.out.printf("Retrieved server certificate %s",
                        metadata.serverCertificateName());
            }

            if(!response.isTruncated()) {
                done = true;
            } else {
                newMarker = response.marker();
            }
        }

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 서버 인증서 업데이트
<a name="update-a-server-certificate"></a>

IamClient의 `updateServerCertificate` 메서드를 호출하여 서버 인증서의 이름이나 경로를 업데이트할 수 있습니다. 이 메서드는 서버 인증서의 현재 이름 및 사용할 새 이름이나 새 경로로 설정된 [UpdateServerCertificateRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateServerCertificateRequest.html) 객체를 사용합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.UpdateServerCertificateRequest;
import software.amazon.awssdk.services.iam.model.UpdateServerCertificateResponse;
```

 **코드** 

```
    public static void updateCertificate(IamClient iam, String curName, String newName) {

        try {
            UpdateServerCertificateRequest request =
                UpdateServerCertificateRequest.builder()
                        .serverCertificateName(curName)
                        .newServerCertificateName(newName)
                        .build();

            UpdateServerCertificateResponse response =
                iam.updateServerCertificate(request);


            System.out.printf("Successfully updated server certificate to name %s",
                newName);

        } catch (IamException e) {
             System.err.println(e.awsErrorDetails().errorMessage());
             System.exit(1);
        }
     }
```

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

## 서버 인증서 삭제
<a name="delete-a-server-certificate"></a>

서버 인증서를 삭제하려면 인증서 이름이 포함된 [DeleteServerCertificateRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DeleteServerCertificateRequest.html)와 함께 IamClient의 `deleteServerCertificate` 메서드를 호출하세요.

 **가져오기** 

```
import software.amazon.awssdk.services.iam.model.DeleteServerCertificateRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **코드** 

```
    public static void deleteCert(IamClient iam,String certName ) {

        try {
            DeleteServerCertificateRequest request =
                DeleteServerCertificateRequest.builder()
                        .serverCertificateName(certName)
                        .build();

            iam.deleteServerCertificate(request);
            System.out.println("Successfully deleted server certificate " +
                    certName);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 추가 정보
<a name="more-information"></a>
+  IAM 사용 설명서의 [서버 인증서 작업](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_server-certs.html) 
+  IAM API 참조의 [GetServerCertificate](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetServerCertificate.html) 
+  IAM API 참조의 [ListServerCertificates](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListServerCertificates.html) 
+  IAM API 참조의 [UpdateServerCertificate](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateServerCertificate.html) 
+  IAM API 참조의 [DeleteServerCertificate](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteServerCertificate.html) 
+  [AWS Certificate Manager 사용 설명서](https://docs.aws.amazon.com/acm/latest/userguide/) 

# Kinesis 작업
<a name="examples-kinesis"></a>

이 단원에서는 AWS SDK for Java 2.x를 사용한 [Amazon Kinesis](https://docs.aws.amazon.com/kinesis/) 프로그래밍의 예제를 제공합니다.

Kinesis에 대한 자세한 내용은 [Amazon Kinesis 개발자 안내서](https://docs.aws.amazon.com/streams/latest/dev/introduction.html)를 참조하세요.

다음 예제에는 각 기술을 보여주는 데 필요한 코드만 포함되어 있습니다. [전체 예제 코드는 GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)에 있습니다. 이 위치에서 단일 소스 파일을 다운로드하거나 리포지토리를 로컬로 복사하여 모든 예제를 빌드하고 실행할 수 있습니다.

**Topics**
+ [Amazon Kinesis Data Streams 가입](examples-kinesis-stream.md)

# Amazon Kinesis Data Streams 가입
<a name="examples-kinesis-stream"></a>

다음 예제에서는 `subscribeToShard` 메서드를 사용하여 Amazon Kinesis 데이터 스트림에서 데이터를 검색하고 처리하는 방법을 보여줍니다. Kinesis Data Streams에서는 이제 향상된 팬아웃 기능과 지연 시간이 짧은 HTTP/2 데이터 검색 API를 갖추어 개발자가 동일한 Kinesis 데이터 스트림에서 여러 개의 지연 시간이 짧은 고성능 애플리케이션을 쉽게 실행할 수 있습니다.

## 설정
<a name="set-up"></a>

먼저 비동기 Kinesis 클라이언트와 [SubscribeToShardRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/SubscribeToShardRequest.html) 객체를 만듭니다. 이러한 객체는 다음 각 예제에서 사용되어 Kinesis 이벤트를 구독합니다.

 **가져옵니다**.

```
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisAsyncClient;
import software.amazon.awssdk.services.kinesis.model.ShardIteratorType;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardEvent;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardEventStream;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardRequest;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardResponse;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardResponseHandler;
```

 ** 코드** 

```
        Region region = Region.US_EAST_1;
        KinesisAsyncClient client = KinesisAsyncClient.builder()
        .region(region)
        .build();

        SubscribeToShardRequest request = SubscribeToShardRequest.builder()
                .consumerARN(CONSUMER_ARN)
                .shardId("arn:aws:kinesis:us-east-1:111122223333:stream/StockTradeStream")
                .startingPosition(s -> s.type(ShardIteratorType.LATEST)).build();
```

## 빌더 인터페이스 사용
<a name="use-the-builder-interface"></a>

`builder` 메서드를 사용하여 [SubscribeToShardResponseHandler](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/SubscribeToShardResponseHandler.html) 생성을 간소화할 수 있습니다.

작성기를 사용하여 전체 인터페이스를 구현하는 대신 메서드 호출을 통해 각 수명 주기 콜백을 설정할 수 있습니다.

 ** 코드** 

```
    private static CompletableFuture<Void> responseHandlerBuilder(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .onComplete(() -> System.out.println("All records stream successfully"))
                // Must supply some type of subscriber
                .subscriber(e -> System.out.println("Received event - " + e))
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

게시자의 더욱 많은 제어를 위해 `publisherTransformer` 메서드를 사용하여 게시자를 사용자 지정할 수 있습니다.

 ** 코드** 

```
    private static CompletableFuture<Void> responseHandlerBuilderPublisherTransformer(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .publisherTransformer(p -> p.filter(e -> e instanceof SubscribeToShardEvent).limit(100))
                .subscriber(e -> System.out.println("Received event - " + e))
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

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

## 사용자 지정 응답 핸들러 사용
<a name="use-a-custom-response-handler"></a>

구독자와 게시자의 완전한 제어를 위해 `SubscribeToShardResponseHandler` 인터페이스를 구현합니다.

이 예제에서는 `onEventStream` 메서드를 구현하고, 이는 게시자에 대한 모든 액세스를 허용합니다. 게시자를 구독자가 출력할 이벤트 레코드로 전환하는 방법을 보여줍니다.

 ** 코드** 

```
    private static CompletableFuture<Void> responseHandlerBuilderClassic(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = new SubscribeToShardResponseHandler() {

            @Override
            public void responseReceived(SubscribeToShardResponse response) {
                System.out.println("Receieved initial response");
            }

            @Override
            public void onEventStream(SdkPublisher<SubscribeToShardEventStream> publisher) {
                publisher
                        // Filter to only SubscribeToShardEvents
                        .filter(SubscribeToShardEvent.class)
                        // Flat map into a publisher of just records
                        .flatMapIterable(SubscribeToShardEvent::records)
                        // Limit to 1000 total records
                        .limit(1000)
                        // Batch records into lists of 25
                        .buffer(25)
                        // Print out each record batch
                        .subscribe(batch -> System.out.println("Record Batch - " + batch));
            }

            @Override
            public void complete() {
                System.out.println("All records stream successfully");
            }

            @Override
            public void exceptionOccurred(Throwable throwable) {
                System.err.println("Error during stream - " + throwable.getMessage());
            }
        };
        return client.subscribeToShard(request, responseHandler);
    }
```

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

## 방문자 인터페이스 사용
<a name="use-the-visitor-interface"></a>

[Visitor](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/SubscribeToShardResponseHandler.Visitor.html) 객체를 사용하여 보고 싶은 특정 이벤트를 구독할 수 있습니다.

 ** 코드** 

```
    private static CompletableFuture<Void> responseHandlerBuilderVisitorBuilder(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler.Visitor visitor = SubscribeToShardResponseHandler.Visitor
                .builder()
                .onSubscribeToShardEvent(e -> System.out.println("Received subscribe to shard event " + e))
                .build();
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .subscriber(visitor)
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

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

## 사용자 지정 구독자 사용
<a name="use-a-custom-subscriber"></a>

사용자 지정 구독자를 구현하여 스트림을 구독할 수도 있습니다.

이 코드 조각은 예제 구독자를 보여줍니다.

 ** 코드** 

```
    private static class MySubscriber implements Subscriber<SubscribeToShardEventStream> {

        private Subscription subscription;
        private AtomicInteger eventCount = new AtomicInteger(0);

        @Override
        public void onSubscribe(Subscription subscription) {
            this.subscription = subscription;
            this.subscription.request(1);
        }

        @Override
        public void onNext(SubscribeToShardEventStream shardSubscriptionEventStream) {
            System.out.println("Received event " + shardSubscriptionEventStream);
            if (eventCount.incrementAndGet() >= 100) {
                // You can cancel the subscription at any time if you wish to stop receiving events.
                subscription.cancel();
            }
            subscription.request(1);
        }

        @Override
        public void onError(Throwable throwable) {
            System.err.println("Error occurred while stream - " + throwable.getMessage());
        }

        @Override
        public void onComplete() {
            System.out.println("Finished streaming all events");
        }
    }
```

다음 코드 스니펫과 같이 사용자 지정 구독자를 `subscribe` 메서드에 전달할 수 있습니다.

 ** 코드** 

```
    private static CompletableFuture<Void> responseHandlerBuilderSubscriber(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .subscriber(MySubscriber::new)
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

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

## Kinesis 데이터 스트림에 데이터 레코드 쓰기
<a name="write-data-records-into-a-kinesis-data-stream"></a>

[KinesisClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/KinesisClient.html) 객체를 이용하면 `putRecords` 메서드를 사용해 Kinesis 데이터 스트림에 데이터 레코드를 쓸 수 있습니다. 이 메서드를 성공적으로 호출하려면 [PutRecordsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/PutRecordsRequest.html) 객체를 만들어야 합니다. 데이터 스트림 이름을 `streamName` 메서드에 전달합니다. 또한 다음 코드 예제와 같이 `putRecords` 메서드를 사용하여 데이터를 전달해야 합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisClient;
import software.amazon.awssdk.services.kinesis.model.PutRecordRequest;
import software.amazon.awssdk.services.kinesis.model.KinesisException;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamResponse;
```

다음 Java 코드 예제에서는 **StockTrade** 객체가 Kinesis 데이터 스트림에 쓸 데이터로 사용됩니다. 이 예제를 실행하기 전에 데이터 스트림이 생성되었는지 확인합니다.

 ** 코드** 

```
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisClient;
import software.amazon.awssdk.services.kinesis.model.PutRecordRequest;
import software.amazon.awssdk.services.kinesis.model.KinesisException;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamResponse;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class StockTradesWriter {
    public static void main(String[] args) {
        final String usage = """

                Usage:
                    <streamName>

                Where:
                    streamName - The Amazon Kinesis data stream to which records are written (for example, StockTradeStream)
                """;

        if (args.length != 1) {
            System.out.println(usage);
            System.exit(1);
        }

        String streamName = args[0];
        Region region = Region.US_EAST_1;
        KinesisClient kinesisClient = KinesisClient.builder()
                .region(region)
                .build();

        // Ensure that the Kinesis Stream is valid.
        validateStream(kinesisClient, streamName);
        setStockData(kinesisClient, streamName);
        kinesisClient.close();
    }

    public static void setStockData(KinesisClient kinesisClient, String streamName) {
        try {
            // Repeatedly send stock trades with a 100 milliseconds wait in between.
            StockTradeGenerator stockTradeGenerator = new StockTradeGenerator();

            // Put in 50 Records for this example.
            int index = 50;
            for (int x = 0; x < index; x++) {
                StockTrade trade = stockTradeGenerator.getRandomTrade();
                sendStockTrade(trade, kinesisClient, streamName);
                Thread.sleep(100);
            }

        } catch (KinesisException | InterruptedException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        System.out.println("Done");
    }

    private static void sendStockTrade(StockTrade trade, KinesisClient kinesisClient,
            String streamName) {
        byte[] bytes = trade.toJsonAsBytes();

        // The bytes could be null if there is an issue with the JSON serialization by
        // the Jackson JSON library.
        if (bytes == null) {
            System.out.println("Could not get JSON bytes for stock trade");
            return;
        }

        System.out.println("Putting trade: " + trade);
        PutRecordRequest request = PutRecordRequest.builder()
                .partitionKey(trade.getTickerSymbol()) // We use the ticker symbol as the partition key, explained in
                                                       // the Supplemental Information section below.
                .streamName(streamName)
                .data(SdkBytes.fromByteArray(bytes))
                .build();

        try {
            kinesisClient.putRecord(request);
        } catch (KinesisException e) {
            System.err.println(e.getMessage());
        }
    }

    private static void validateStream(KinesisClient kinesisClient, String streamName) {
        try {
            DescribeStreamRequest describeStreamRequest = DescribeStreamRequest.builder()
                    .streamName(streamName)
                    .build();

            DescribeStreamResponse describeStreamResponse = kinesisClient.describeStream(describeStreamRequest);

            if (!describeStreamResponse.streamDescription().streamStatus().toString().equals("ACTIVE")) {
                System.err.println("Stream " + streamName + " is not active. Please wait a few moments and try again.");
                System.exit(1);
            }

        } catch (KinesisException e) {
            System.err.println("Error found while describing the stream " + streamName);
            System.err.println(e);
            System.exit(1);
        }
    }
}
```

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

## 타사 라이브러리 사용
<a name="use-a-third-party-library"></a>

사용자 지정 구독자를 구현하는 대신 타사 라이브러리를 사용할 수 있습니다. 다음은 RxJava 구현을 사용하는 방법에 대한 예입니다. 그렇지만 반응형 스트림 인터페이스를 구현하는 모든 라이브러리를 사용할 수 있습니다. 해당 라이브러리에 대한 자세한 내용은 [GitHub의 RxJava 위키 페이지](https://github.com/ReactiveX/RxJava/wiki)를 참조하십시오.

라이브러리를 사용하려면 종속성으로 추가합니다. 이 예에서는 Maven을 사용하는 경우에 사용할 POM 조각을 알려줍니다.

 **POM 항목** 

```
<dependency>
 <groupId>io.reactivex.rxjava2</groupId>
 <artifactId>rxjava</artifactId>
 <version>2.2.21</version>
</dependency>
```

 **가져옵니다**.

```
import java.net.URI;
import java.util.concurrent.CompletableFuture;

import io.reactivex.Flowable;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisAsyncClient;
import software.amazon.awssdk.services.kinesis.model.ShardIteratorType;
import software.amazon.awssdk.services.kinesis.model.StartingPosition;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardEvent;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardRequest;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardResponseHandler;
import software.amazon.awssdk.utils.AttributeMap;
```

이 예제에서는 `onEventStream` 수명 주기 메서드에서 RxJava를 사용합니다. 이는 게시자에 대한 모든 액세스를 제공하고, 이를 사용하여 Rx Flowable을 생성할 수 있습니다.

 ** 코드** 

```
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
            .builder()
            .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
            .onEventStream(p -> Flowable.fromPublisher(p)
                                        .ofType(SubscribeToShardEvent.class)
                                        .flatMapIterable(SubscribeToShardEvent::records)
                                        .limit(1000)
                                        .buffer(25)
                                        .subscribe(e -> System.out.println("Record batch = " + e)))
            .build();
```

또한 다음과 같이 `publisherTransformer` 게시자를 포함하여 `Flowable` 메서드를 사용할 수 있습니다. 다음 예제에 표시된 것과 같이 `Flowable` 게시자를 *SdkPublisher*로 조정해야 합니다.

 ** 코드** 

```
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
            .builder()
            .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
            .publisherTransformer(p -> SdkPublisher.adapt(Flowable.fromPublisher(p).limit(100)))
            .build();
```

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

## 추가 정보
<a name="more-information"></a>
+  Amazon Kinesis API 참조의 [SubscribeToShardEvent](https://docs.aws.amazon.com/kinesis/latest/APIReference/API_SubscribeToShardEvent.html)
+  Amazon Kinesis API 참조의 [SubscribeToShard](https://docs.aws.amazon.com/kinesis/latest/APIReference/API_SubscribeToShard.html)

# AWS Lambda 함수 호출, 나열 및 삭제
<a name="examples-lambda"></a>

이 섹션에서는 AWS SDK for Java 2.x를 사용하여 Lambda 서비스 클라이언트를 프로그래밍하는 예를 제공합니다.

**Topics**
+ [Lambda 함수를 호출합니다.](#invoke-function)
+ [Lambda 함수 나열](#list-function)
+ [Lambda 함수 삭제](#delete-function)

## Lambda 함수를 호출합니다.
<a name="invoke-function"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html) 객체를 생성하고 해당 `invoke` 메서드를 호출하여 Lambda 함수를 호출할 수 있습니다. [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/InvokeRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/InvokeRequest.html) 객체를 생성하여 함수에 전달할 Lambda 함수 이름 및 페이로드와 같은 추가 정보를 지정합니다. 함수 이름은 *arn:aws:lambda:us-east-1:123456789012:function:HelloFunction*과 같이 나타납니다. AWS Management Console에서 함수를 확인해 값을 검색할 수 있습니다.

함수에 페이로드 데이터를 전달하려면 정보가 포함된 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/SdkBytes.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/SdkBytes.html) 객체를 만듭니다. 예를 들어 다음 코드 예제에서는 Lambda 함수에 JSON 데이터가 전달됩니다.

 **가져오기** 

```
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.lambda.model.InvokeResponse;
import software.amazon.awssdk.services.lambda.model.LambdaException;
```

 **코드** 

다음 코드 예제에서는 Lambda 함수를 호출하는 방법을 보여줍니다.

```
    public static void invokeFunction(LambdaClient awsLambda, String functionName) {

         InvokeResponse res = null ;
        try {
            //Need a SdkBytes instance for the payload
            String json = "{\"Hello \":\"Paris\"}";
            SdkBytes payload = SdkBytes.fromUtf8String(json) ;

            //Setup an InvokeRequest
            InvokeRequest request = InvokeRequest.builder()
                    .functionName(functionName)
                    .payload(payload)
                    .build();

            res = awsLambda.invoke(request);
            String value = res.payload().asUtf8String() ;
            System.out.println(value);

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

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

## Lambda 함수 나열
<a name="list-function"></a>

`[Lambda Client](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html)` 객체를 구축하고 `listFunctions` 메서드를 간접적으로 호출합니다. 이 메서드는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/ListFunctionsResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/ListFunctionsResponse.html) 객체를 반환합니다. [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/FunctionConfiguration.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/FunctionConfiguration.html) 객체 목록을 반환하도록 이 객체의 `functions` 메서드를 간접적으로 호출할 수 있습니다. 목록을 반복하여 함수에 대한 정보를 검색할 수 있습니다. 예를 들어 다음 Java 코드 예제는 각 함수 이름을 가져오는 방법을 보여줍니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.LambdaException;
import software.amazon.awssdk.services.lambda.model.ListFunctionsResponse;
import software.amazon.awssdk.services.lambda.model.FunctionConfiguration;
import java.util.List;
```

 **코드** 

다음 Java 코드 예제는 함수 이름 목록을 검색하는 방법을 보여 줍니다.

```
    public static void listFunctions(LambdaClient awsLambda) {

        try {
            ListFunctionsResponse functionResult = awsLambda.listFunctions();
            List<FunctionConfiguration> list = functionResult.functions();

            for (FunctionConfiguration config: list) {
                System.out.println("The function name is "+config.functionName());
            }

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

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

## Lambda 함수 삭제
<a name="delete-function"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html) 객체를 구축하고 `deleteFunction` 메서드를 간접적으로 호출합니다. [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/DeleteFunctionRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/DeleteFunctionRequest.html) 객체를 만들고 `deleteFunction` 메서드에 전달합니다. 이 개체에는 삭제할 함수의 이름과 같은 정보가 포함되어 있습니다. 함수 이름은 *arn:aws:lambda:us-east-1:123456789012:function:HelloFunction*과 같이 나타납니다. AWS Management Console에서 함수를 확인해 값을 검색할 수 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.model.DeleteFunctionRequest;
import software.amazon.awssdk.services.lambda.model.LambdaException;
```

 **코드** 

다음 Java 코드는 Lambda 함수를 삭제하는 방법을 보여줍니다.

```
    public static void deleteLambdaFunction(LambdaClient awsLambda, String functionName ) {
        try {
            DeleteFunctionRequest request = DeleteFunctionRequest.builder()
                    .functionName(functionName)
                    .build();

            awsLambda.deleteFunction(request);
            System.out.println("The "+functionName +" function was deleted");

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

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

# Amazon S3와 작업
<a name="examples-s3"></a>

이 섹션에서는 AWS SDK for Java 2.x를 사용하여 Amazon S3로 작업하는 경우의 배경 정보를 제공합니다. 이 섹션에서는 이 가이드의 *코드 예제* 섹션에 제시된 [Amazon S3 Java v2 예제](java_s3_code_examples.md)를 보완합니다.

## 의 S3 클라이언트 AWS SDK for Java 2.x
<a name="s3-clients"></a>

는 다양한 유형의 S3 클라이언트를 AWS SDK for Java 2.x 제공합니다. 다음 표는 차이점을 보여주며 이를 통해 사용 사례에 가장 적합한 것을 결정할 수 있습니다.


**Amazon S3 클라이언트의 다양한 종류**  

| S3 클라이언트 | 간단한 설명 | 사용해야 하는 경우 | 제한 및 단점 | 
| --- | --- | --- | --- | 
|  **AWS CRT 기반 S3 클라이언트** 인터페이스: [S3AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) Builder: [S3CrtAsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html) [성능 S3 클라이언트: AWS CRT 기반 S3 클라이언트 사용](crt-based-s3-client.md)을(를) 참조하세요.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html)  | 
|  **멀티파트가 *사용 설정된* Java 기반 S3 비동기식 클라이언트** 인터페이스: [S3AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) Builder: [S3AsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html) [병렬 전송을 사용하도록 Java 기반 S3 비동기식 클라이언트 구성](s3-async-client-multipart.md)을(를) 참조하세요.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html)  |  AWS CRT 기반 S3 클라이언트보다 성능이 떨어집니다. | 
|  **멀티파트가 *사용 설정되지 않은* Java 기반 S3 비동기식 클라이언트** 인터페이스: [S3AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) Builder: [S3AsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html) |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html)  |  성능 최적화가 필요하지 않습니다.  | 
|  **Java 기반 S3 동기식 클라이언트** 인터페이스: [S3Client](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html) Builder: [S3ClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3ClientBuilder.html) |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3.html)  |  성능 최적화가 필요하지 않습니다.  | 

**참고**  
버전 2.18.x 이상에서는 엔드포인트 재정의를 포함할 때 [가상 호스팅 방식 주소 지정](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access)을 AWS SDK for Java 2.x 사용합니다. 이는 버킷 이름이 유효한 DNS 레이블인 한 적용됩니다.  
`true`에서 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3BaseClientBuilder.html#forcePathStyle(java.lang.Boolean](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3BaseClientBuilder.html#forcePathStyle(java.lang.Boolean) 메서드를 호출하여 클라이언트가 버킷에 경로 스타일 주소 지정을 사용하도록 강제합니다.  
다음 예제는 엔드포인트 재정의 및 경로 스타일 주소 지정을 사용하여 구성된 서비스 클라이언트를 보여줍니다.  

```
S3Client client = S3Client.builder()
                          .region(Region.US_WEST_2)
                          .endpointOverride(URI.create("https://s3.us-west-2.amazonaws.com"))
                          .forcePathStyle(true)
                          .build();
```

**Topics**
+ [SDK의 S3 클라이언트](#s3-clients)
+ [S3에 스트림 업로드](best-practices-s3-uploads.md)
+ [미리 서명된 URL](examples-s3-presign.md)
+ [교차 리전 액세스](s3-cross-region.md)
+ [체크섬을 통한 데이터 무결성 보호](s3-checksums.md)
+ [고성능 S3 클라이언트 사용](crt-based-s3-client.md)
+ [병렬 전송 지원 구성](s3-async-client-multipart.md)
+ [파일 및 디렉터리 전송](transfer-manager.md)
+ [S3 이벤트 알림](examples-s3-event-notifications.md)

# 를 사용하여 Amazon S3에 스트림 업로드 AWS SDK for Java 2.x
<a name="best-practices-s3-uploads"></a>

스트림을 사용하여 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest,software.amazon.awssdk.core.sync.RequestBody)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest,software.amazon.awssdk.core.sync.RequestBody)) 또는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest,software.amazon.awssdk.core.sync.RequestBody)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest,software.amazon.awssdk.core.sync.RequestBody))를 사용하여 S3에 콘텐츠를 업로드하는 경우 동기식 API의 `RequestBody` 팩토리 클래스를 사용하여 스트림을 제공합니다. 비동기식 API의 경우 `AsyncRequestBody`는 동등한 팩토리 클래스입니다.

## 스트림을 업로드하는 방법
<a name="s3-stream-upload-methods"></a>

동기식 API의 경우 다음과 같은 `RequestBody`의 팩토리 메서드를 사용하여 스트림을 제공할 수 있습니다.
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromInputStream(java.io.InputStream,long))(InputStream inputStream, long contentLength)`

  `[fromContentProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromContentProvider(software.amazon.awssdk.http.ContentStreamProvider,long,java.lang.String))(ContentStreamProvider provider, long contentLength, String mimeType)`
  + `ContentStreamProvider`에는 `fromInputStream(InputStream inputStream)` 팩토리 메서드가 있습니다.
+ `[fromContentProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromContentProvider(software.amazon.awssdk.http.ContentStreamProvider,java.lang.String))(ContentStreamProvider provider, String mimeType)`

비동기식 API의 경우 다음과 같은 `AsyncRequestBody`의 팩토리 메서드를 사용할 수 있습니다.
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(java.io.InputStream,java.lang.Long,java.util.concurrent.ExecutorService))(InputStream inputStream, Long contentLength, ExecutorService executor)` 
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(software.amazon.awssdk.core.async.AsyncRequestBodyFromInputStreamConfiguration))(AsyncRequestBodyFromInputStreamConfiguration configuration)`
  + AsyncRequestBodyFromInputStreamConfiguration.Builder를 사용하여 스트림을 제공합니다.
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(java.util.function.Consumer))(Consumer<AsyncRequestBodyFromInputStreamConfiguration.Builder> configuration)`
+ `[forBlockingInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#forBlockingInputStream(java.lang.Long))(Long contentLength)`
  + 결과 `[BlockingInputStreamAsyncRequestBody](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/BlockingInputStreamAsyncRequestBody.html)`에는 스트림을 제공하는 데 사용할 수 있는 `writeInputStream(InputStream inputStream)` 메서드가 포함되어 있습니다.

## 업로드 수행
<a name="s3-upload-stream-perform"></a>

### 스트림의 길이를 알고 있는 경우
<a name="s3-stream-upload-supply-content-length"></a>

이전에 표시된 메서드의 서명에서 볼 수 있듯이 대부분의 메서드는 콘텐츠 길이 파라미터를 허용합니다.

바이트 단위로 콘텐츠 길이를 알고 있는 경우 정확한 값을 입력합니다.

```
// Always provide the exact content length when it's available.
long contentLength = 1024; // Exact size in bytes.
s3Client.putObject(req -> req
    .bucket("amzn-s3-demo-bucket")
    .key("my-key"),
RequestBody.fromInputStream(inputStream, contentLength));
```

**주의**  
 입력 스트림에서 업로드할 때 지정된 콘텐츠 길이가 실제 바이트 수와 일치하지 않으면 다음과 같은 상황이 발생할 수 있습니다.  
잘린 객체 - 지정된 길이가 너무 작을 경우
업로드 실패 또는 연결 중단 - 지정된 길이가 너무 큰 경우

### 스트림 길이를 모르는 경우
<a name="s3-stream-upload-unknown-length"></a>

#### 동기식 API 사용
<a name="s3-upload-unknown-sync-client"></a>

`fromContentProvider(ContentStreamProvider provider, String mimeType)`를 사용합니다.

```
public PutObjectResponse syncClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3Client s3Client = S3Client.create();

    RequestBody body = RequestBody.fromContentProvider(ContentStreamProvider.fromInputStream(inputStream), "text/plain");
    PutObjectResponse putObjectResponse = s3Client.putObject(b -> b.bucket(BUCKET_NAME).key(KEY_NAME), body);
    return putObjectResponse;
}
```

SDK는 메모리의 전체 스트림을 버퍼링하여 콘텐츠 길이를 계산하므로 대용량 스트림에서 메모리 문제가 발생할 수 있습니다. 동기식 클라이언트로 대용량 스트림을 업로드해야 하는 경우 멀티파트 API를 사용하는 것이 좋습니다.

##### 동기식 클라이언트 API 및 멀티파트 API를 사용하여 스트림 업로드
<a name="sync-multipart-upload-stream"></a>

```
public static void uploadStreamToS3(String bucketName, String key, InputStream inputStream) {
    // Create S3 client
    S3Client s3Client = S3Client.create();
    try {
        // Step 1: Initiate the multipart upload
        CreateMultipartUploadRequest createMultipartUploadRequest = CreateMultipartUploadRequest.builder()
                .bucket(bucketName)
                .key(key)
                .build();

        CreateMultipartUploadResponse createResponse = s3Client.createMultipartUpload(createMultipartUploadRequest);
        String uploadId = createResponse.uploadId();
        System.out.println("Started multipart upload with ID: " + uploadId);

        // Step 2: Upload parts
        List<CompletedPart> completedParts = new ArrayList<>();
        int partNumber = 1;
        byte[] buffer = new byte[PART_SIZE];
        int bytesRead;

        try {
            while ((bytesRead = readFullyOrToEnd(inputStream, buffer)) > 0) {
                // Create request to upload a part
                UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                        .bucket(bucketName)
                        .key(key)
                        .uploadId(uploadId)
                        .partNumber(partNumber)
                        .build();

                // If we didn't read a full buffer, create a properly sized byte array
                RequestBody requestBody;
                if (bytesRead < PART_SIZE) {
                    byte[] lastPartBuffer = new byte[bytesRead];
                    System.arraycopy(buffer, 0, lastPartBuffer, 0, bytesRead);
                    requestBody = RequestBody.fromBytes(lastPartBuffer);
                } else {
                    requestBody = RequestBody.fromBytes(buffer);
                }

                // Upload the part and save the response's ETag
                UploadPartResponse uploadPartResponse = s3Client.uploadPart(uploadPartRequest, requestBody);
                CompletedPart part = CompletedPart.builder()
                        .partNumber(partNumber)
                        .eTag(uploadPartResponse.eTag())
                        .build();
                completedParts.add(part);

                System.out.println("Uploaded part " + partNumber + " with size " + bytesRead + " bytes");
                partNumber++;
            }

            // Step 3: Complete the multipart upload
            CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder()
                    .parts(completedParts)
                    .build();

            CompleteMultipartUploadRequest completeRequest = CompleteMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .multipartUpload(completedMultipartUpload)
                    .build();

            CompleteMultipartUploadResponse completeResponse = s3Client.completeMultipartUpload(completeRequest);
            System.out.println("Multipart upload completed. Object URL: " + completeResponse.location());

        } catch (Exception e) {
            // If an error occurs, abort the multipart upload
            System.err.println("Error during multipart upload: " + e.getMessage());
            AbortMultipartUploadRequest abortRequest = AbortMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .build();
            s3Client.abortMultipartUpload(abortRequest);
            System.err.println("Multipart upload aborted");
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                System.err.println("Error closing input stream: " + e.getMessage());
            }
        }
    } finally {
        s3Client.close();
    }
}

/**
 * Reads from the input stream into the buffer, attempting to fill the buffer completely
 * or until the end of the stream is reached.
 *
 * @param inputStream the input stream to read from
 * @param buffer      the buffer to fill
 * @return the number of bytes read, or -1 if the end of the stream is reached before any bytes are read
 * @throws IOException if an I/O error occurs
 */
private static int readFullyOrToEnd(InputStream inputStream, byte[] buffer) throws IOException {
    int totalBytesRead = 0;
    int bytesRead;

    while (totalBytesRead < buffer.length) {
        bytesRead = inputStream.read(buffer, totalBytesRead, buffer.length - totalBytesRead);
        if (bytesRead == -1) {
            break; // End of stream
        }
        totalBytesRead += bytesRead;
    }

    return totalBytesRead > 0 ? totalBytesRead : -1;
}
```

**참고**  
대부분의 사용 사례에서는 크기를 알 수 없는 스트림에 비동기식 클라이언트 API를 사용하는 것이 좋습니다. 이 접근 방식은 병렬 전송을 사용하고 더 간단한 프로그래밍 인터페이스를 제공합니다. 스트림이 큰 경우 SDK가 스트림 분할을 멀티파트 청크로 처리하기 때문입니다.  
멀티파트가 사용 설정된 표준 S3 비동기식 클라이언트와 AWS CRT 기반 S3 클라이언트 모두이 접근 방식을 구현합니다. 다음 섹션에서는 이 접근 방식의 예제를 보여줍니다.

#### 동기식 API 사용
<a name="s3-stream-upload-unknown-async-client"></a>

`fromInputStream(InputStream inputStream, Long contentLength, ExecutorService executor)`에 대한 `contentLength` 인수의 `null` 값을 제공할 수 있습니다.

**Example AWS CRT 기반 비동기 클라이언트 사용:**  

```
public PutObjectResponse crtClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor);  // 'null' indicates that the
                                                                                            // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null){
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

**Example 멀티파트가 사용 설정된 표준 비동기식 클라이언트 사용:**  

```
public PutObjectResponse asyncClient_multipart_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.builder().multipartEnabled(true).build();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor); // 'null' indicates that the
                                                                                           // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null) {
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

# Amazon S3 미리 서명된 URLs 작업
<a name="examples-s3-presign"></a>

미리 서명된 URLs 사용자가 AWS 자격 증명이나 권한을 가질 필요 없이 프라이빗 S3 객체에 대한 임시 액세스를 제공합니다.

예를 들어 Alice가 S3 객체에 대한 액세스 권한을 가지고 있고 해당 객체에 대한 액세스 권한을 Bob과 일시적으로 공유하려고 할 경우, Alice는 미리 서명된 GET 요청을 생성하여 Bob과 공유할 수 있으므로 Bob은 Alice의 보안 인증에 액세스하지 않고도 객체를 다운로드할 수 있습니다. HTTP GET 요청과 HTTP PUT 요청에 대해 미리 서명된 URL을 생성할 수 있습니다.

## 객체에 대해 미리 서명된 URL을 생성한 다음 다운로드(GET 요청)합니다.
<a name="get-presignedobject"></a>

다음 예시는 두 부분으로 구성되어 있습니다.
+ 1부: Alice가 객체의 미리 서명된 URL을 생성합니다.
+ 2부: Bob은 미리 서명된 URL을 사용하여 객체를 다운로드합니다.

### 1부: URL 생성
<a name="get-presigned-object-part1"></a>

Alice는 이미 S3 버킷에 객체를 보유하고 있으며 다음 코드를 사용하여 Bob이 후속 GET 요청에서 사용할 수 있는 URL 문자열을 생성합니다.

#### 가져오기
<a name="get-presigned-example-imports"></a>

```
import com.example.s3.util.PresignUrlUtils;
import org.slf4j.Logger;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import software.amazon.awssdk.utils.IoUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.UUID;
```

```
    /* Create a pre-signed URL to download an object in a subsequent GET request. */
    public String createPresignedGetUrl(String bucketName, String keyName) {
        try (S3Presigner presigner = S3Presigner.create()) {

            GetObjectRequest objectRequest = GetObjectRequest.builder()
                    .bucket(bucketName)
                    .key(keyName)
                    .build();

            GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofMinutes(10))  // The URL will expire in 10 minutes.
                    .getObjectRequest(objectRequest)
                    .build();

            PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest);
            logger.info("Presigned URL: [{}]", presignedRequest.url().toString());
            logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method());

            return presignedRequest.url().toExternalForm();
        }
    }
```

### 2부: 객체 다운로드
<a name="get-presigned-object-part2"></a>

Bob은 다음 세 가지 코드 옵션 중 하나를 사용하여 객체를 다운로드합니다. 또는 브라우저를 사용하여 GET 요청을 수행할 수도 있습니다.

#### JDK `HttpURLConnection`(v1.1 이후) 사용
<a name="get-presigned-example-useHttpUrlConnection"></a>

```
    /* Use the JDK HttpURLConnection (since v1.1) class to do the download. */
    public byte[] useHttpUrlConnectionToGet(String presignedUrlString) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.

        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
            connection.setRequestMethod("GET");
            // Download the result of executing the request.
            try (InputStream content = connection.getInputStream()) {
                IoUtils.copy(content, byteArrayOutputStream);
            }
            logger.info("HTTP response code is " + connection.getResponseCode());

        } catch (S3Exception | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

#### JDK `HttpClient`(v11 이후) 사용
<a name="get-presigned-example-useHttpClient"></a>

```
    /* Use the JDK HttpClient (since v11) class to do the download. */
    public byte[] useHttpClientToGet(String presignedUrlString) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
        HttpClient httpClient = HttpClient.newHttpClient();
        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpResponse<InputStream> response = httpClient.send(requestBuilder
                            .uri(presignedUrl.toURI())
                            .GET()
                            .build(),
                    HttpResponse.BodyHandlers.ofInputStream());

            IoUtils.copy(response.body(), byteArrayOutputStream);

            logger.info("HTTP response code is " + response.statusCode());

        } catch (URISyntaxException | InterruptedException | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

#### SDK for Java의 `SdkHttpClient` 사용
<a name="get-presigned-example-useSdkHttpClient"></a>

```
    /* Use the AWS SDK for Java SdkHttpClient class to do the download. */
    public byte[] useSdkHttpClientToGet(String presignedUrlString) {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.
        try {
            URL presignedUrl = new URL(presignedUrlString);
            SdkHttpRequest request = SdkHttpRequest.builder()
                    .method(SdkHttpMethod.GET)
                    .uri(presignedUrl.toURI())
                    .build();

            HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
                    .request(request)
                    .build();

            try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) {
                HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
                response.responseBody().ifPresentOrElse(
                        abortableInputStream -> {
                            try {
                                IoUtils.copy(abortableInputStream, byteArrayOutputStream);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        },
                        () -> logger.error("No response body."));

                logger.info("HTTP Response code is {}", response.httpResponse().statusCode());
            }
        } catch (URISyntaxException | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/GeneratePresignedGetUrlAndRetrieve.java) 및 [테스트](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/test/java/com/example/s3/presignurl/GeneratePresignedGetUrlTests.java)를 참조하세요.

## 업로드를 위해 미리 서명된 URL을 생성한 다음 파일을 업로드(PUT 요청)합니다.
<a name="put-presignedobject"></a>

다음 예시는 두 부분으로 구성되어 있습니다.
+ 1부: Alice는 객체를 업로드하기 위해 미리 서명된 URL을 생성합니다.
+ 2부: Bob이 미리 서명된 URL을 사용하여 파일을 업로드합니다.

### 1부: URL 생성
<a name="put-presigned-object-part1"></a>

Alice는 이미 S3 버킷을 보유하고 있으며 다음 코드를 사용하여 Bob이 후속 PUT 요청에서 사용할 수 있는 URL 문자열을 생성합니다.

#### 가져오기
<a name="put-presigned-example-imports"></a>

```
import com.example.s3.util.PresignUrlUtils;
import org.slf4j.Logger;
import software.amazon.awssdk.core.internal.sync.FileContentStreamProvider;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
```

```
    /* Create a presigned URL to use in a subsequent PUT request */
    public String createPresignedUrl(String bucketName, String keyName, Map<String, String> metadata) {
        try (S3Presigner presigner = S3Presigner.create()) {

            PutObjectRequest objectRequest = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(keyName)
                    .metadata(metadata)
                    .build();

            PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofMinutes(10))  // The URL expires in 10 minutes.
                    .putObjectRequest(objectRequest)
                    .build();


            PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(presignRequest);
            String myURL = presignedRequest.url().toString();
            logger.info("Presigned URL to upload a file to: [{}]", myURL);
            logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method());

            return presignedRequest.url().toExternalForm();
        }
    }
```

### 2부: 파일 객체 업로드
<a name="put-presigned-object-part2"></a>

Bob은 다음 세 가지 코드 옵션 중 하나를 사용하여 파일을 업로드합니다.

#### JDK `HttpURLConnection`(v1.1 이후) 사용
<a name="put-presigned-example-useHttpUrlConnection"></a>

```
    /* Use the JDK HttpURLConnection (since v1.1) class to do the upload. */
    public void useHttpUrlConnectionToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());
        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
            connection.setDoOutput(true);
            metadata.forEach((k, v) -> connection.setRequestProperty("x-amz-meta-" + k, v));
            connection.setRequestMethod("PUT");
            OutputStream out = connection.getOutputStream();

            try (RandomAccessFile file = new RandomAccessFile(fileToPut, "r");
                 FileChannel inChannel = file.getChannel()) {
                ByteBuffer buffer = ByteBuffer.allocate(8192); //Buffer size is 8k

                while (inChannel.read(buffer) > 0) {
                    buffer.flip();
                    for (int i = 0; i < buffer.limit(); i++) {
                        out.write(buffer.get());
                    }
                    buffer.clear();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }

            out.close();
            connection.getResponseCode();
            logger.info("HTTP response code is " + connection.getResponseCode());

        } catch (S3Exception | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

#### JDK `HttpClient`(v11 이후) 사용
<a name="put-presigned-example-useHttpClient"></a>

```
    /* Use the JDK HttpClient (since v11) class to do the upload. */
    public void useHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
        metadata.forEach((k, v) -> requestBuilder.header("x-amz-meta-" + k, v));

        HttpClient httpClient = HttpClient.newHttpClient();
        try {
            final HttpResponse<Void> response = httpClient.send(requestBuilder
                            .uri(new URL(presignedUrlString).toURI())
                            .PUT(HttpRequest.BodyPublishers.ofFile(Path.of(fileToPut.toURI())))
                            .build(),
                    HttpResponse.BodyHandlers.discarding());

            logger.info("HTTP response code is " + response.statusCode());

        } catch (URISyntaxException | InterruptedException | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

#### SDK for Java의 `SdkHttpClient` 사용
<a name="put-presigned-example-useSdkHttpClient"></a>

```
    /* Use the AWS SDK for Java V2 SdkHttpClient class to do the upload. */
    public void useSdkHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());

        try {
            URL presignedUrl = new URL(presignedUrlString);

            SdkHttpRequest.Builder requestBuilder = SdkHttpRequest.builder()
                    .method(SdkHttpMethod.PUT)
                    .uri(presignedUrl.toURI());
            // Add headers
            metadata.forEach((k, v) -> requestBuilder.putHeader("x-amz-meta-" + k, v));
            // Finish building the request.
            SdkHttpRequest request = requestBuilder.build();

            HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
                    .request(request)
                    .contentStreamProvider(new FileContentStreamProvider(fileToPut.toPath()))
                    .build();

            try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) {
                HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
                logger.info("Response code: {}", response.httpResponse().statusCode());
            }
        } catch (URISyntaxException | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/GeneratePresignedUrlAndPutFileWithMetadata.java) 및 [테스트](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/test/java/com/example/s3/presignurl/GeneratePresignedPutUrlTests.java)를 참조하세요.

# Amazon S3를 위한 교차 리전 액세스
<a name="s3-cross-region"></a>

Amazon Simple Storage Service(Amazon S3) 버킷으로 작업하면 일반적으로 버킷의 AWS 리전를 알 수 있습니다. 사용하는 리전전은 S3 클라이언트를 생성할 때 결정됩니다.

하지만 특정 버킷으로 작업해야 하는데 해당 버킷이 S3 클라이언트에 설정된 동일한 리전전에 있는지 알 수 없는 경우가 있습니다.

버킷 리전전을 결정하기 위해 더 많은 호출을 하는 대신 SDK를 사용하여 여러 리전전의 S3 버킷에 액세스할 수 있도록 할 수 있습니다.

## 설정
<a name="s3-cross-region-setup"></a>

SDK `2.20.111` 버전에서 교차 리전 액세스에 대한 지원을 사용할 수 있게 되었습니다. 다음 코드 조각과 같이 Maven 빌드 파일의 `s3` 종속 항목에 대해 이 버전 또는 이후 버전을 사용하세요.

```
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3</artifactId>
  <version>2.27.21</version>
</dependency>
```

다음으로 S3 클라이언트를 생성할 때 코드 조각에 표시된 대로 교차 리전 액세스를 활성화하세요. 기본적으로 액세스가 활성화되어 있지 않습니다.

```
S3AsyncClient client = S3AsyncClient.builder()
                                    .crossRegionAccessEnabled(true)
                                    .build();
```

## SDK가 교차 리전 액세스를 제공하는 방법
<a name="s3-cross-region-routing"></a>

`putObject` 메서드를 사용할 때와 같이 요청에서 기존 버킷을 참조하면 SDK가 클라이언트용으로 구성된 리전전에 대한 요청을 시작합니다.

특정 리전전에 버킷이 없는 경우 오류 응답에는 버킷이 있는 실제 리전전이 포함됩니다. 그러면 SDK는 두 번째 요청에서 올바른 리전전을 사용합니다.

동일한 버킷에 대한 향후 요청을 최적화하기 위해 SDK는 이 리전전 매핑을 클라이언트에 캐시합니다.

## 고려 사항
<a name="s3-cross-region-considerations"></a>

교차 리전 버킷 액세스를 활성화하는 경우, 버킷이 클라이언트의 구성된 리전전에 있지 않으면 첫 번째 API 호출 시 지연 시간이 늘어날 수 있다는 점에 유의하세요. 하지만 후속 호출은 캐시된 리전전 정보를 활용하므로 성능이 향상됩니다.

교차 리전 액세스를 활성화해도 버킷 액세스는 영향을 받지 않습니다. 사용자는 버킷이 상주하는 리전전에 상관없이 버킷에 액세스할 수 있는 권한을 부여받아야 합니다.

# 체크섬을 통한 데이터 무결성 보호
<a name="s3-checksums"></a>

Amazon Simple Storage Service(S3)는 객체를 업로드할 때 체크섬을 지정하는 기능을 제공합니다. 체크섬을 지정하면 객체와 함께 저장되며 객체를 다운로드할 때 유효성을 검사할 수 있습니다.

체크섬은 파일을 전송할 때 데이터 무결성을 한층 더 강화합니다. 체크섬을 사용하면 수신된 파일이 원본 파일과 일치하는지 확인하여 데이터 일관성을 확인할 수 있습니다. Amazon S3의 체크섬에 대한 자세한 내용은 [지원되는 알고리즘](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html#using-additional-checksums)을 포함한 [Amazon Simple Storage Service 사용 설명서](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html)를 참조하세요.

필요에 가장 적합한 알고리즘을 유연하게 선택하고 SDK가 체크섬을 계산하도록 할 수 있습니다. 또는 지원되는 알고리즘 중 하나를 사용하여 미리 계산된 체크섬 값을 제공할 수 있습니다.

**참고**  
 AWS SDK for Java 2.x의 버전 2.30.0부터 SDK는 업로드에 대한 `CRC32` 체크섬을 자동으로 계산하여 기본 무결성 보호를 제공합니다. 사전 계산된 체크섬 값을 제공하지 않거나 SDK가 체크섬을 계산하는 데 사용해야 하는 알고리즘을 지정하지 않은 경우 SDK는이 체크섬을 계산합니다.   
또한 SDK는 [AWS SDK 및 도구 참조 안내서](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)에서 확인할 수 있고 외부에서 설정할 수 있는 데이터 무결성 보호에 대한 전역 설정을 지원합니다.

체크섬은 객체 업로드와 객체 다운로드라는 두 가지 요청 단계로 설명합니다.

## 객체 업로드
<a name="use-service-S3-checksum-upload"></a>

 `putObject` 메서드를 사용하여 객체를 업로드하고 체크섬 알고리즘을 제공하면 SDK가 지정된 알고리즘의 체크섬을 계산합니다.

다음 코드 조각은 `SHA256` 체크섬이 있는 객체를 업로드하라는 요청을 보여줍니다. SDK는 요청을 보내면 `SHA256` 체크섬을 계산하고 객체를 업로드합니다. Amazon S3는 체크섬을 계산하고 SDK에서 제공하는 체크섬과 비교하여 콘텐츠의 무결성을 확인합니다. Amazon S3는 객체와 함께 체크섬을 저장합니다.

```
public void putObjectWithChecksum() {
        s3Client.putObject(b -> b
                .bucket(bucketName)
                .key(key)
                .checksumAlgorithm(ChecksumAlgorithm.SHA256),
            RequestBody.fromString("This is a test"));
}
```

요청에 체크섬 알고리즘을 제공하지 않는 경우 체크섬 동작은 다음 표와 같이 사용하는 SDK 버전에 따라 달라집니다.

**체크섬 알고리즘이 제공되지 않은 경우 체크섬 동작**


| Java SDK 버전 | 체크섬 동작 | 
| --- | --- | 
| 2.30.0 이하 | SDK는 CRC 기반 체크섬을 자동으로 계산하여 요청에 제공하지 않습니다. | 
| 2.30.0 이상 | SDK는 `CRC32` 알고리즘을 사용하여 체크섬을 계산하고 요청에 제공합니다. Amazon S3는 자체 `CRC32` 체크섬을 계산하여 전송의 무결성을 확인하고 이를 SDK에서 제공하는 체크섬과 비교합니다. 체크섬이 일치하면 체크섬이 객체와 함께 저장됩니다. | 

### 미리 계산된 체크섬 값 사용
<a name="use-service-S3-checksum-upload-pre"></a>

요청과 함께 제공되는 사전 계산된 체크섬 값은 SDK의 자동 계산을 비활성화하고 제공된 값을 대신 사용합니다.

다음 예제에서는 사전 계산된 SHA256 체크섬이 있는 요청을 보여줍니다.

```
    public void putObjectWithPrecalculatedChecksum(String filePath) {
        String checksum = calculateChecksum(filePath, "SHA-256");

        s3Client.putObject((b -> b
                .bucket(bucketName)
                .key(key)
                .checksumSHA256(checksum)),
            RequestBody.fromFile(Paths.get(filePath)));
    }
```

Amazon S3에서 체크섬 값이 지정된 알고리즘에 대해 올바르지 않다고 판단하면 서비스는 오류 응답을 반환합니다.

### 멀티파트 업로드
<a name="use-service-S3-checksum-upload-multi"></a>

멀티파트 업로드에 체크섬을 사용할 수도 있습니다.

 Java 2.x용 SDK는 멀티파트 업로드에 체크섬을 사용하는 두 가지 옵션을 제공합니다. 첫 번째 옵션은 `S3TransferManager`를 사용합니다.

다음 전송 관리자 예제는 업로드를 위한 SHA1 알고리즘을 지정합니다.

```
    public void multipartUploadWithChecksumTm(String filePath) {
        S3TransferManager transferManager = S3TransferManager.create();
        UploadFileRequest uploadFileRequest = UploadFileRequest.builder()
            .putObjectRequest(b -> b
                .bucket(bucketName)
                .key(key)
                .checksumAlgorithm(ChecksumAlgorithm.SHA1))
            .source(Paths.get(filePath))
            .build();
        FileUpload fileUpload = transferManager.uploadFile(uploadFileRequest);
        fileUpload.completionFuture().join();
        transferManager.close();
    }
```

업로드에 전송 관리자를 사용할 때 체크섬 알고리즘을 제공하지 않으면 SDK는 `CRC32` 알고리즘을 기반으로 체크섬을 자동으로 계산합니다. SDK는 SDK의 모든 버전에서 이 계산을 수행합니다.

두 번째 옵션은 [`S3Client` API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html)(또는 [`S3AsyncClient` API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html))를 사용하여 멀티파트 업로드를 수행합니다. 이 접근법으로 추가 체크섬을 지정하는 경우 업로드를 시작할 때 사용할 알고리즘을 지정해야 합니다. 또한 각 파트 요청에 대한 알고리즘을 지정하고 업로드 후 각 파트에 대해 계산된 체크섬을 제공해야 합니다.

```
    public void multipartUploadWithChecksumS3Client(String filePath) {
        ChecksumAlgorithm algorithm = ChecksumAlgorithm.CRC32;

        // Initiate the multipart upload.
        CreateMultipartUploadResponse createMultipartUploadResponse = s3Client.createMultipartUpload(b -> b
            .bucket(bucketName)
            .key(key)
            .checksumAlgorithm(algorithm)); // Checksum specified on initiation.
        String uploadId = createMultipartUploadResponse.uploadId();

        // Upload the parts of the file.
        int partNumber = 1;
        List<CompletedPart> completedParts = new ArrayList<>();
        ByteBuffer bb = ByteBuffer.allocate(1024 * 1024 * 5); // 5 MB byte buffer

        try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
            long fileSize = file.length();
            long position = 0;
            while (position < fileSize) {
                file.seek(position);
                long read = file.getChannel().read(bb);

                bb.flip(); // Swap position and limit before reading from the buffer.
                UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .checksumAlgorithm(algorithm) // Checksum specified on each part.
                    .partNumber(partNumber)
                    .build();

                UploadPartResponse partResponse = s3Client.uploadPart(
                    uploadPartRequest,
                    RequestBody.fromByteBuffer(bb));

                CompletedPart part = CompletedPart.builder()
                    .partNumber(partNumber)
                    .checksumCRC32(partResponse.checksumCRC32()) // Provide the calculated checksum.
                    .eTag(partResponse.eTag())
                    .build();
                completedParts.add(part);

                bb.clear();
                position += read;
                partNumber++;
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }

        // Complete the multipart upload.
        s3Client.completeMultipartUpload(b -> b
            .bucket(bucketName)
            .key(key)
            .uploadId(uploadId)
            .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build()));
    }
```

[전체 예제 코드](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/PerformMultiPartUpload.java) 및 [테스트](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/test/java/com/example/s3/PerformMultiPartUploadTests.java) 코드는 GitHub 코드 예제 저장소에 있습니다.

## 객체 다운로드
<a name="use-service-S3-checksum-download"></a>

[getObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#getObject(software.amazon.awssdk.services.s3.model.GetObjectRequest)) 메서드를 사용하여 객체를 다운로드하면, `GetObjectRequest`용 빌더의 `checksumMode` 메서드가 `ChecksumMode.ENABLED`로 설정된 경우. 

다음 스니펫의 요청은 체크섬을 계산하고 값을 비교하여 응답의 체크섬을 검증하도록 SDK에 지시합니다.

```
    public GetObjectResponse getObjectWithChecksum() {
        return s3Client.getObject(b -> b
                        .bucket(bucketName)
                        .key(key)
                        .checksumMode(ChecksumMode.ENABLED))
                .response();
    }
```

**참고**  
체크섬과 함께 객체를 업로드하지 않은 경우 검증이 수행되지 않습니다.

## 기타 체크섬 계산 옵션
<a name="S3-checsum-calculation-options"></a>

**참고**  
전송된 데이터의 데이터 무결성을 확인하고 전송 오류를 식별하려면 사용자가 체크섬 계산 옵션에 대한 SDK 기본 설정을 유지하는 것이 좋습니다. 기본적으로 SDK는 `PutObject` 및 `GetObject`를 포함한 많은 S3 작업에 대해 이 중요한 검사를 추가합니다.

그러나 Amazon S3를 사용하려면 최소한의 체크섬 확인이 필요한 경우 기본 구성 설정을 변경하여 많은 검사를 비활성화할 수 있습니다.

### 필요하지 않은 경우 자동 체크섬 계산 비활성화
<a name="S3-minimize-checksum-calc-global"></a>

`PutObject` 및 `GetObject`와 같이 이를 지원하는 작업에 대해 SDK에서 자동 체크섬 계산을 비활성화할 수 있습니다. 그러나 일부 S3 작업에는 체크섬 계산이 필요하므로 이러한 작업에 대한 체크섬 계산을 비활성화할 수 없습니다.

SDK는 요청의 페이로드와 응답의 페이로드에 대한 체크섬 계산을 위한 별도의 설정을 제공합니다.

다음 목록은 다양한 범위에서 체크섬 계산을 최소화하는 데 사용할 수 있는 설정을 설명합니다.
+ **모든 애플리케이션 범위** - 환경 변수 또는 공유 AWS `config` 및 `credentials` 파일의 프로파일에서 설정을 변경하면 모든 애플리케이션에서 이러한 설정을 사용할 수 있습니다. 애플리케이션 또는 서비스 클라이언트 범위에서 재정의되지 않는 한 이러한 설정은 모든 AWS SDK 애플리케이션의 모든 서비스 클라이언트에 영향을 줍니다.
  + 프로파일에 설정을 추가합니다.

    ```
    [default]
    request_checksum_calculation = WHEN_REQUIRED
    response_checksum_validation = WHEN_REQUIRED
    ```
  + 환경 변수를 추가합니다.

    ```
    AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED
    AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED
    ```
+ **현재 애플리케이션 범위** - Java 시스템 속성(`aws.requestChecksumCalculation`)을 `WHEN_REQUIRED`로 설정하여 체크섬 계산을 제한할 수 있습니다. 응답에 해당하는 시스템 속성은 `aws.responseChecksumValidation`입니다.

  이러한 설정은 서비스 클라이언트를 만드는 중에 재정의되지 않는 한 애플리케이션의 모든 SDK 서비스 클라이언트에 영향을 줍니다.

  애플리케이션 시작 시 시스템 속성을 설정합니다.

  ```
  import software.amazon.awssdk.core.SdkSystemSetting;
  import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
  import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
  import software.amazon.awssdk.services.s3.S3Client;
  
  class DemoClass {
      public static void main(String[] args) {
  
          System.setProperty(SdkSystemSetting.AWS_REQUEST_CHECKSUM_CALCULATION.property(), // Resolves to "aws.requestChecksumCalculation".
                  "WHEN_REQUIRED");
          System.setProperty(SdkSystemSetting.AWS_RESPONSE_CHECKSUM_VALIDATION.property(), // Resolves to "aws.responseChecksumValidation".
                  "WHEN_REQUIRED");
  
          S3Client s3Client = S3Client.builder().build();
  
          // Use s3Client.
      }
  }
  ```
+ **단일 S3 서비스 클라이언트 범위** - 빌더 메서드를 사용하여 최소 체크섬 양을 계산하도록 단일 S3 서비스 클라이언트를 구성할 수 있습니다.

  ```
  import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
  import software.amazon.awssdk.services.s3.S3Client;
  
  public class RequiredChecksums {
      public static void main(String[] args) {
          S3Client s3 = S3Client.builder()
                  .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
                  .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
                  .build();
  
          // Use s3Client. 
      }
  // ...
  }
  ```

### 간소화된 MD5 호환성을 위해 `LegacyMd5Plugin` 사용
<a name="S3-checksum-legacy-md5"></a>

버전 2.30.0의 CRC32 체크섬 동작 릴리스와 함께 SDK는 필요한 작업에 대한 MD5 체크섬 계산을 중단했습니다.

S3 작업에 레거시 MD5 체크섬 동작이 필요한 경우 SDK 버전 2.31.32에서 릴리스된 `LegacyMd5Plugin`을 사용할 수 있습니다.

`LegacyMd5Plugin`은 레거시 MD5 체크섬 동작에 의존하는 애플리케이션과의 호환성을 유지해야 할 때 유용합니다. 특히 S3A 파일 시스템 커넥터(Apache Spark, Iceberg)와 함께 사용되는 서드 파티 S3 호환 스토리지 공급자와 함께 작업할 때 유용합니다.

`LegacyMd5Plugin`을 사용하려면 S3 클라이언트 빌더에 추가합니다.

```
// For synchronous S3 client.
S3Client s3Client = S3Client.builder()
                           .addPlugin(LegacyMd5Plugin.create())
                           .build();

// For asynchronous S3 client.
S3AsyncClient asyncClient = S3AsyncClient.builder()
                                       .addPlugin(LegacyMd5Plugin.create())
                                       .build();
```

체크섬이 필요한 작업에 MD5 체크섬을 추가하고 체크섬을 지원하지만 필요하지 않은 작업에 대한 SDK 기본 체크섬 추가를 건너뛰려면 `ClientBuilder` 옵션 `requestChecksumCalculation` 및 `responseChecksumValidation`을 `WHEN_REQUIRED`로 사용할 수 있습니다. 이렇게 하면 체크섬이 필요한 작업에만 SDK 기본 체크섬이 추가됩니다.

```
// Use the `LegacyMd5Plugin` with `requestChecksumCalculation` and `responseChecksumValidation` set to WHEN_REQUIRED.
S3AsyncClient asyncClient = S3AsyncClient.builder()
                                       .addPlugin(LegacyMd5Plugin.create())
                                       .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
                                       .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
                                       .build();
```

이 구성은 최신 체크섬 알고리즘을 완전히 지원하지는 않지만 특정 작업에 대해 여전히 MD5 체크섬이 필요한 서드 파티 S3 호환 스토리지 시스템으로 작업할 때 특히 유용합니다.

# 성능 S3 클라이언트: AWS CRT 기반 S3 클라이언트 사용
<a name="crt-based-s3-client"></a>

[AWS 공통 런타임(CRT)](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)을 기반으로 구축된 AWS CRT 기반 S3 클라이언트는 대체 S3 비동기 클라이언트입니다. Amazon S3의 [멀티파트 업로드 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html)와 [바이트 범위 가져오기](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance-guidelines.html#optimizing-performance-guidelines-get-range)를 자동으로 사용하여 향상된 성능과 안정성을 바탕으로 Amazon Simple Storage Service(Amazon S3)와 객체를 주고 받습니다.

 AWS CRT 기반 S3 클라이언트는 네트워크 장애가 발생할 경우 전송 신뢰성을 개선합니다. 전송을 처음부터 다시 시작하지 않고 파일 전송의 실패한 개별 부분을 다시 시도하여 안정성이 향상됩니다.

또한 AWS CRT 기반 S3 클라이언트는 향상된 연결 풀링 및 도메인 이름 시스템(DNS) 로드 밸런싱을 제공하여 처리량도 개선합니다.

SDK의 표준 S3 비동기식 클라이언트 대신 AWS CRT 기반 S3 클라이언트를 사용하고 처리량 향상을 즉시 활용할 수 있습니다.

**중요**  
 AWS CRT 기반 S3 클라이언트는 현재 클라이언트 수준이나 요청 수준에서 [SDK 지표 수집](metrics.md)을 지원하지 않습니다.

**AWS SDK의 CRT 기반 구성 요소**

이 주제에 설명된 AWS CRT 기반* S3* 클라이언트와 AWS CRT 기반 *HTTP* 클라이언트는 SDK의 서로 다른 구성 요소입니다.

**AWS CRT 기반 S3 클라이언트**는 [S3AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 인터페이스를 구현한 것으로, Amazon S3 서비스를 사용하는 데 사용됩니다. 이는 `S3AsyncClient` 인터페이스의 Java 기반 구현의 대안이며 여러 가지 이점을 제공합니다.

[AWS CRT 기반 HTTP 클라이언트](http-configuration-crt.md)는 [SdkAsyncHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/async/SdkAsyncHttpClient.html) 인터페이스를 구현한 것으로, 일반 HTTP 통신에 사용됩니다. 이는 `SdkAsyncHttpClient` 인터페이스의 Netty 구현의 대안이며 여러 가지 이점을 제공합니다.

두 구성 요소 모두 [AWS Common Runtime](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)의 라이브러리를 사용하지만 AWS CRT 기반 S3 클라이언트는 [aws-c-s3 라이브러리](https://github.com/awslabs/aws-c-s3)를 사용하고 [S3 멀티파트 업로드 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 기능을 지원합니다. AWS CRT 기반 HTTP 클라이언트는 범용이므로 S3 멀티파트 업로드 API 기능을 지원하지 않습니다.

## AWS CRT 기반 S3 클라이언트를 사용하기 위한 종속성 추가
<a name="crt-based-s3-client-depend"></a>

 AWS CRT 기반 S3 클라이언트를 사용하려면 Maven 프로젝트 파일에 다음 두 가지 종속성을 추가합니다. 예제는 사용하는 최소 버전을 보여 줍니다. Maven 중앙 리포지토리에서 가장 최신 버전의 [s3](https://central.sonatype.com/artifact/software.amazon.awssdk/s3) 및 [aws-crt](https://central.sonatype.com/artifact/software.amazon.awssdk.crt/aws-crt) 아티팩트를 검색하세요.

```
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3</artifactId>
  <version>2.27.21</version>
</dependency>
<dependency>
  <groupId>software.amazon.awssdk.crt</groupId>
  <artifactId>aws-crt</artifactId>
  <version>0.30.11</version>
</dependency>
```

## AWS CRT 기반 S3 클라이언트의 인스턴스 생성
<a name="crt-based-s3-client-create"></a>

 다음 코드 조각과 같이 기본 설정을 사용하여 AWS CRT 기반 S3 클라이언트의 인스턴스를 생성합니다.

```
S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
```

클라이언트를 구성하려면 AWS CRT 클라이언트 빌더를 사용합니다. 빌더 메서드를 변경하여 표준 S3 비동기 클라이언트에서 AWS CRT 기반 클라이언트로 전환할 수 있습니다.

```
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;


S3AsyncClient s3AsyncClient = 
        S3AsyncClient.crtBuilder()
                     .credentialsProvider(DefaultCredentialsProvider.create())
                     .region(Region.US_WEST_2)
                     .targetThroughputInGbps(20.0)
                     .minimumPartSizeInBytes(8 * 1025 * 1024L)
                     .build();
```

**참고**  
표준 빌더의 일부 설정은 AWS CRT 클라이언트 빌더에서 현재 지원되지 않을 수 있습니다. `S3AsyncClient#builder()`를 호출하여 표준 빌더를 가져오세요.

## AWS CRT 기반 S3 클라이언트 사용
<a name="crt-based-s3-client-use"></a>

 AWS CRT 기반 S3 클라이언트를 사용하여 Amazon S3 API 작업을 호출합니다. 다음 예제는 AWS SDK for Java를 통해 사용할 수 있는 [PutObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html#putObject(java.util.function.Consumer,software.amazon.awssdk.core.async.AsyncRequestBody)) 및 [GetObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html#getObject(java.util.function.Consumer,software.amazon.awssdk.core.async.AsyncResponseTransformer)) 작업을 보여줍니다.

```
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;


S3AsyncClient s3Client = S3AsyncClient.crtCreate();

// Upload a local file to Amazon S3.
PutObjectResponse putObjectResponse = 
      s3Client.putObject(req -> req.bucket(<BUCKET_NAME>)
                                   .key(<KEY_NAME>),
                        AsyncRequestBody.fromFile(Paths.get(<FILE_NAME>)))
              .join();

// Download an object from Amazon S3 to a local file.
GetObjectResponse getObjectResponse = 
      s3Client.getObject(req -> req.bucket(<BUCKET_NAME>)
                                   .key(<KEY_NAME>),
                        AsyncResponseTransformer.toFile(Paths.get(<FILE_NAME>)))
              .join();
```

## 알 수 없는 크기의 스트림 업로드
<a name="crt-stream-unknown-size"></a>

 AWS AWS CRT 기반 S3 클라이언트의 한 가지 중요한 이점은 크기가 알려지지 않은 입력 스트림을 효율적으로 처리할 수 있다는 것입니다. 이는 총 크기를 미리 확인할 수 없는 소스에서 데이터를 업로드해야 할 때 특히 유용합니다.

```
public PutObjectResponse crtClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor);  // 'null' indicates that the
                                                                                            // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null){
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

이 기능은 잘못된 콘텐츠 길이 사양으로 인해 객체가 잘리거나 업로드가 실패할 수 있는 기존 업로드의 일반적인 문제를 방지하는 데 도움이 됩니다.

## 구성 제한 사항
<a name="crt-based-s3-client-limitations"></a>

 AWS CRT 기반 S3 클라이언트와 Java 기반 S3 비동기 클라이언트는 성능 엣지를 제공하는 AWS CRT 기반 S3 클라이언트와 [유사한 기능을 제공합니다](examples-s3.md#s3-clients). 그러나 AWS CRT 기반 S3 클라이언트에는 Java 기반 S3 비동기 클라이언트에 있는 구성 설정이 없습니다. 이러한 설정은 다음과 같습니다.
+ *클라이언트 수준 구성:* API 직접 호출 시도 제한 시간, 압축 실행 인터셉터, 지표 게시자, 사용자 지정 실행 속성, 사용자 지정 고급 옵션, 사용자 지정된 예약 실행기 서비스, 사용자 지정 헤더
+ *요청 수준 구성:* 사용자 지정 서명자, API 호출 시도 제한 시간

구성 차이의 전체 목록은 API 참조를 참조하세요.


| Java 기반 S3 비동기식 클라이언트 | AWS CRT 기반 S3 클라이언트 | 
| --- | --- | 
| 클라이언트 수준 구성[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/crt-based-s3-client.html)요청 수준 구성[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/crt-based-s3-client.html) | 클라이언트 수준 구성[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/crt-based-s3-client.html)요청 수준 구성[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/crt-based-s3-client.html) | 

# 병렬 전송을 사용하도록 Java 기반 S3 비동기식 클라이언트 구성
<a name="s3-async-client-multipart"></a>

버전 2.27.5부터 표준 Java 기반 S3 비동기식 클라이언트는 자동 병렬 전송(멀티파트 업로드 및 다운로드)을 지원합니다. Java 기반 S3 비동기식 클라이언트를 만들 때 병렬 전송에 대한 지원을 구성합니다.

이 섹션에서는 병렬 전송을 사용하는 방법과 구성을 사용자 지정하는 방법을 보여줍니다.

## `S3AsyncClient`의 인스턴스를 만듭니다.
<a name="s3-async-client-multipart-create"></a>

[빌더](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html)에서 `multipart*` 메서드를 직접적으로 호출하지 않고 `S3AsyncClient` 인스턴스를 만들면 병렬 전송이 사용되지 않습니다. 다음 각 문은 멀티파트 업로드 및 다운로드를 지원하지 않고 Java 기반 S3 비동기식 클라이언트를 만듭니다.

### 멀티파트 지원 *없이* 만들기
<a name="s3-async-client-mp-off"></a>

**Example**  

```
import software.amazon.awssdk.auth.credentials.ProcessCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;


S3AsyncClient s3Client = S3AsyncClient.create();

S3AsyncClient s3Client2 = S3AsyncClient.builder().build();

S3AsyncClient s3Client3 = S3AsyncClient.builder()
        .credentialsProvider(ProcessCredentialsProvider.builder().build())
        .region(Region.EU_NORTH_1)
        .build();
```

### 멀티파트 지원*으로* 만들기
<a name="s3-async-client-mp-on"></a>

기본 설정으로 병렬 전송을 사용하려면 다음 예제와 같이 빌더에서 `true`를 직접적으로 호출하고 `multipartEnabled`에 전달합니다.

**Example**  

```
S3AsyncClient s3AsyncClient2 = S3AsyncClient.builder()
        .multipartEnabled(true)
        .build();
```

`thresholdInBytes` 및 `minimumPartSizeInBytes` 설정의 기본값은 8MiB입니다.

멀티파트 설정을 사용자 지정하면 다음과 같이 병렬 전송이 자동으로 사용됩니다.

**Example**  

```
import software.amazon.awssdk.services.s3.S3AsyncClient;
import static software.amazon.awssdk.transfer.s3.SizeConstant.MB;


S3AsyncClient s3AsyncClient2 = S3AsyncClient.builder()
        .multipartConfiguration(b -> b
                .thresholdInBytes(16 * MB)
                .minimumPartSizeInBytes(10 * MB))
        .build();
```

## 알 수 없는 크기의 스트림 업로드
<a name="java-async-client-stream-unknown-size"></a>

멀티파트가 사용 설정된 Java 기반 S3 비동기식 클라이언트는 총 크기를 미리 알 수 없는 입력 스트림을 효율적으로 처리할 수 있습니다.

```
public PutObjectResponse asyncClient_multipart_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.builder().multipartEnabled(true).build();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor); // 'null' indicates that the
                                                                                           // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null) {
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

이 접근 방식은 잘린 객체 또는 실패한 업로드와 같이 잘못된 콘텐츠 길이를 수동으로 지정할 때 발생할 수 있는 문제를 방지합니다.

# Amazon S3 Transfer Manager로 파일 및 디렉터리 전송
<a name="transfer-manager"></a>

Amazon S3 Transfer Manager는 AWS SDK for Java 2.x를 위한 오픈 소스의 고급 파일 전송 유틸리티입니다. 이를 사용하여 Amazon Simple Storage Service(Amazon S3)와 파일 및 디렉터리를 주고받을 수 있습니다.

[AWS CRT 기반 S3 클라이언트](crt-based-s3-client.md) 또는 [표준 Java 기반 S3 비동기식 클라이언트(멀티파트 사용 설정)](s3-async-client-multipart.md)를 기반으로 구축된 S3 Transfer Manager는 [멀티파트 업로드](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 및 [바이트 범위 가져오기](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance-guidelines.html#optimizing-performance-guidelines-get-range)와 같은 성능 개선의 이점을 활용할 수 있습니다.

S3 Transfer Manager를 사용하면 전송 진행 상황을 실시간으로 모니터링하고 나중에 실행하기 위해 전송을 일시 중지할 수도 있습니다.

## 시작하기
<a name="transfer-manager-prerequisites"></a>

### 빌드 파일에 종속성을 추가
<a name="transfer-manager-add-dependency"></a>

향상된 멀티파트 성능으로 S3 Transfer Manager를 사용하려면 필요한 종속성으로 빌드 파일을 구성합니다.

------
#### [ Use the AWS CRT-based S3 client ]

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.27.211</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-transfer-manager</artifactId>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk.crt</groupId>
        <artifactId>aws-crt</artifactId>
        <version>0.29.1432</version>
    </dependency>
</dependencies>
```

1 [최신 버전](https://central.sonatype.com/artifact/software.amazon.awssdk/bom). 2[최신 버전](https://central.sonatype.com/artifact/software.amazon.awssdk.crt/aws-crt).

------
#### [ Use the Java-based S3 async client ]

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.27.211</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-transfer-manager</artifactId>
    </dependency>
</dependencies>
```

1 [최신 버전](https://central.sonatype.com/artifact/software.amazon.awssdk/bom).

------

### S3 Transfer Manager 인스턴스를 생성
<a name="transfer-manager-create"></a>

병렬 전송을 활성화하려면 AWS CRT 기반 S3 클라이언트 또는 멀티파트가 활성화된 Java 기반 S3 비동기 클라이언트를 전달해야 합니다. 다음 예제에서는 사용자 지정 설정으로 S3 Transfer Manager를 구성하는 방법을 보여줍니다.

------
#### [ Use the AWS CRT-based S3 client ]

```
        S3AsyncClient s3AsyncClient = S3AsyncClient.crtBuilder()
                .credentialsProvider(DefaultCredentialsProvider.create())
                .region(Region.US_EAST_1)
                .targetThroughputInGbps(20.0)
                .minimumPartSizeInBytes(8 * MB)
                .build();

        S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

------
#### [ Use the Java-based S3 async client ]

`aws-crt` 종속성이 빌드 파일에 포함되지 않은 경우 S3 Transfer Manager는 SDK for Java 2.x에서 사용되는 표준 S3 비동기식 클라이언트를 기반으로 구축됩니다.

**S3 클라이언트의 사용자 지정 구성 - 멀티파트 사용 필요**

```
        S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
                .multipartEnabled(true)
                .credentialsProvider(DefaultCredentialsProvider.create())
                .region(Region.US_EAST_1)
                .build();

        S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

**S3 클라이언트 구성 없음 - 멀티파트 지원이 자동으로 사용 설정됨**

```
S3TransferManager transferManager = S3TransferManager.create();
```

------

## S3 버킷으로 파일을 업로드하려면
<a name="transfer-manager-upload"></a>

다음 예제는 업로드 진행 상황을 기록하는 [LoggingTransferListener](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/progress/LoggingTransferListener.html)의 선택적 사용과 함께 파일 업로드 예제를 보여줍니다.

S3 Transfer Manager를 사용하여 Amazon S3에 파일을 업로드하려면 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadFileRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadFileRequest.html)객체를 `S3TransferManager`의 [uploadFile](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#uploadFile(software.amazon.awssdk.transfer.s3.model.UploadFileRequest)) 메서드에 전달하세요.

`uploadFile` 메서드에서 반환된 [FileUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/FileUpload.html) 객체는 업로드 프로세스를 나타냅니다. 요청이 완료되면 [CompletedFileUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedFileUpload.html) 객체에는 업로드에 대한 정보가 포함됩니다.

```
    public void trackUploadFile(S3TransferManager transferManager, String bucketName,
                             String key, URI filePathURI) {
        UploadFileRequest uploadFileRequest = UploadFileRequest.builder()
                .putObjectRequest(b -> b.bucket(bucketName).key(key))
                .addTransferListener(LoggingTransferListener.create())  // Add listener.
                .source(Paths.get(filePathURI))
                .build();

        FileUpload fileUpload = transferManager.uploadFile(uploadFileRequest);

        fileUpload.completionFuture().join();
        /*
            The SDK provides a LoggingTransferListener implementation of the TransferListener interface.
            You can also implement the interface to provide your own logic.

            Configure log4J2 with settings such as the following.
                <Configuration status="WARN">
                    <Appenders>
                        <Console name="AlignedConsoleAppender" target="SYSTEM_OUT">
                            <PatternLayout pattern="%m%n"/>
                        </Console>
                    </Appenders>

                    <Loggers>
                        <logger name="software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener" level="INFO" additivity="false">
                            <AppenderRef ref="AlignedConsoleAppender"/>
                        </logger>
                    </Loggers>
                </Configuration>

            Log4J2 logs the progress. The following is example output for a 21.3 MB file upload.
                Transfer initiated...
                |                    | 0.0%
                |====                | 21.1%
                |============        | 60.5%
                |====================| 100.0%
                Transfer complete!
        */
    }
```

### 가져오기
<a name="transfer-manager-upload-imports"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedFileUpload;
import software.amazon.awssdk.transfer.s3.model.FileUpload;
import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.UUID;
```

## S3 버킷에서 파일 다운로드
<a name="transfer-manager-download"></a>

다음 예시는 다운로드 예시와 함께 다운로드 진행 상황을 기록하는 [LoggingTransferListener](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/progress/LoggingTransferListener.html)의 선택적 사용을 보여줍니다.

S3 Transfer Manager를 사용하여 S3 버킷에서 객체를 다운로드하려면 [DownloadFileRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DownloadFileRequest.html) 객체를 만들어 [downloadFile](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#downloadFile(software.amazon.awssdk.transfer.s3.model.DownloadFileRequest)) 메서드에 전달합니다.

`S3TransferManager`의 `downloadFile` 메서드에서 반환된 [FileDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/FileDownload.html) 객체는 파일 전송을 나타냅니다. 다운로드가 완료되면 [CompletedFileDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedFileDownload.html)에는 다운로드에 대한 정보에 대한 액세스 권한이 포함됩니다.

```
    public void trackDownloadFile(S3TransferManager transferManager, String bucketName,
                             String key, String downloadedFileWithPath) {
        DownloadFileRequest downloadFileRequest = DownloadFileRequest.builder()
                .getObjectRequest(b -> b.bucket(bucketName).key(key))
                .addTransferListener(LoggingTransferListener.create())  // Add listener.
                .destination(Paths.get(downloadedFileWithPath))
                .build();

        FileDownload downloadFile = transferManager.downloadFile(downloadFileRequest);

        CompletedFileDownload downloadResult = downloadFile.completionFuture().join();
        /*
            The SDK provides a LoggingTransferListener implementation of the TransferListener interface.
            You can also implement the interface to provide your own logic.

            Configure log4J2 with settings such as the following.
                <Configuration status="WARN">
                    <Appenders>
                        <Console name="AlignedConsoleAppender" target="SYSTEM_OUT">
                            <PatternLayout pattern="%m%n"/>
                        </Console>
                    </Appenders>

                    <Loggers>
                        <logger name="software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener" level="INFO" additivity="false">
                            <AppenderRef ref="AlignedConsoleAppender"/>
                        </logger>
                    </Loggers>
                </Configuration>

            Log4J2 logs the progress. The following is example output for a 21.3 MB file download.
                Transfer initiated...
                |=======             | 39.4%
                |===============     | 78.8%
                |====================| 100.0%
                Transfer complete!
        */
    }
```

### 가져오기
<a name="transfer-manager-download-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedFileDownload;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
import software.amazon.awssdk.transfer.s3.model.FileDownload;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
```

## 다른 버킷에 Amazon S3 객체를 추가
<a name="transfer-manager-copy"></a>

다음 예는 S3 Transfer Manager로 객체를 복사하는 방법을 보여줍니다.

S3 버킷에서 다른 버킷으로 객체 복사를 시작하려면 기본 [CopyObjectRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/model/CopyObjectRequest.html) 인스턴스를 생성하세요.

다음으로, 기본 `CopyObjectRequest` 항목을 S3 Transfer Manager가사용할 수 있는 [CopyRequest로](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CopyRequest.html) 래핑합니다.

`S3TransferManager`의 `copy` 메서드에서 반환된 `Copy` 객체는 복사 프로세스를 나타냅니다. 복사 프로세스가 완료되면 [CompletedCopy](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedCopy.html) 객체에는 응답에 대한 세부 정보가 포함됩니다.

```
    public String copyObject(S3TransferManager transferManager, String bucketName,
            String key, String destinationBucket, String destinationKey) {
        CopyObjectRequest copyObjectRequest = CopyObjectRequest.builder()
                .sourceBucket(bucketName)
                .sourceKey(key)
                .destinationBucket(destinationBucket)
                .destinationKey(destinationKey)
                .build();

        CopyRequest copyRequest = CopyRequest.builder()
                .copyObjectRequest(copyObjectRequest)
                .build();

        Copy copy = transferManager.copy(copyRequest);

        CompletedCopy completedCopy = copy.completionFuture().join();
        return completedCopy.response().copyObjectResult().eTag();
    }
```

**참고**  
S3 Transfer Manager를 사용하여 리전 간 복사를 수행하려면 다음 코드 조각`crossRegionAccessEnabled`과 같이 AWS CRT 기반 S3 클라이언트 빌더에서를 활성화합니다.  

```
S3AsyncClient s3AsyncClient = S3AsyncClient.crtBuilder()
                .crossRegionAccessEnabled(true)
                .build();

S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

### 가져오기
<a name="transfer-manager-copy-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedCopy;
import software.amazon.awssdk.transfer.s3.model.Copy;
import software.amazon.awssdk.transfer.s3.model.CopyRequest;

import java.util.UUID;
```

## S3 버킷에 로컬 디렉토리 업로드
<a name="transfer-manager-upload_directory"></a>

다음 예는 로컬 디렉터리를 S3에 업로드하는 방법을 보여줍니다.

먼저 `S3TransferManager` 인스턴스의 [uploadDirectory](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#uploadDirectory(software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest)) 메서드를 호출하여 [UploadDirectoryRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadDirectoryRequest.html)를 전달합니다.

[DirectoryUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DirectoryUpload.html) 객체는 업로드 프로세스를 나타내며, 요청이 완료되면 [CompletedDirectoryUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedDirectoryUpload.html)를 생성합니다. `CompleteDirectoryUpload` 객체에는 전송에 실패한 파일을 포함하여 전송 결과에 대한 정보가 들어 있습니다.

```
    public Integer uploadDirectory(S3TransferManager transferManager,
            URI sourceDirectory, String bucketName) {
        DirectoryUpload directoryUpload = transferManager.uploadDirectory(UploadDirectoryRequest.builder()
                .source(Paths.get(sourceDirectory))
                .bucket(bucketName)
                .build());

        CompletedDirectoryUpload completedDirectoryUpload = directoryUpload.completionFuture().join();
        completedDirectoryUpload.failedTransfers()
                .forEach(fail -> logger.warn("Object [{}] failed to transfer", fail.toString()));
        return completedDirectoryUpload.failedTransfers().size();
    }
```

### 가져오기
<a name="transfer-manager-upload_directory-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.DirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.UUID;
```

## 로컬 디렉터리로 S3 버킷 객체 다운로드
<a name="transfer-manager-download_directory"></a>

다음 예시와 같이 S3 버킷에 있는 객체를 로컬 디렉터리로 다운로드할 수 있습니다.

S3 버킷의 객체를 로컬 디렉토리로 다운로드하려면 먼저 Transfer Manager의 [DownloadDirectory](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#downloadDirectory(software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest)) 메서드를 호출하고 [DownloadDirectoryRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DownloadDirectoryRequest.html)를 전달하세요.

[DirectoryDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DirectoryDownload.html) 객체는 업로드 프로세스를 나타내며, 요청이 완료되면 [CompletedDirectoryDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedDirectoryDownload.html)를 생성합니다. `CompleteDirectoryDownload` 객체에는 전송에 실패한 파일을 포함하여 전송 결과에 대한 정보가 들어 있습니다.

```
    public Integer downloadObjectsToDirectory(S3TransferManager transferManager,
            URI destinationPathURI, String bucketName) {
        DirectoryDownload directoryDownload = transferManager.downloadDirectory(DownloadDirectoryRequest.builder()
                .destination(Paths.get(destinationPathURI))
                .bucket(bucketName)
                .build());
        CompletedDirectoryDownload completedDirectoryDownload = directoryDownload.completionFuture().join();

        completedDirectoryDownload.failedTransfers()
                .forEach(fail -> logger.warn("Object [{}] failed to transfer", fail.toString()));
        return completedDirectoryDownload.failedTransfers().size();
    }
```

### 가져오기
<a name="transfer-manager-download_directory-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.DirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
```

## 전체 예제 보기
<a name="transfer-manager-example-location"></a>

이 페이지의 모든 예제에 대한 [전체 코드는 GitHub에 포함](https://github.com/awsdocs/aws-doc-sdk-examples/tree/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/transfermanager)되어 있습니다.

# S3 이벤트 알림 작업
<a name="examples-s3-event-notifications"></a>

버킷의 활동을 모니터링하는 데 도움이 되도록 Amazon S3는 특정 이벤트가 발생할 때 알림을 보낼 수 있습니다. Amazon S3 사용 설명서에서는 [버킷이 보낼 수 있는 알림](https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html#notification-how-to-overview)에 대한 정보를 얻을 수 있습니다.

SDK for Java를 사용하여 4개의 가능한 대상으로 이벤트를 보내도록 버킷을 설정할 수 있습니다.
+ Amazon Simple Notification Service(Amazon SNS) 주제
+ Amazon Simple Queue Service 대기열
+ AWS Lambda 함수
+ Amazon EventBridge

EventBridge로 이벤트를 전송하도록 버킷을 설정할 때 동일한 이벤트를 여러 대상으로 팬아웃하도록 EventBridge 규칙을 구성할 수 있습니다. 처음 3개 대상 중 하나로 직접 전송하도록 버킷을 구성하는 경우 각 이벤트에 하나의 대상 유형만 지정 가능합니다.

다음 섹션에서는 SDK for Java를 사용하여 Amazon SQS 대기열과 EventBridge라는 2가지 방법으로 S3 이벤트 알림을 전송하도록 버킷을 구성하는 방법을 알아봅니다.

마지막 섹션에서는 S3 이벤트 알림 API를 사용하여 객체 중심 방식으로 알림을 사용하는 방법을 보여줍니다.

## 대상으로 직접 전송하도록 버킷 구성
<a name="s3-event-conf-bucket-direct"></a>

다음 예제에서는 *객체 만들기* 이벤트 또는 객체 *태그 지정* 이벤트가 버킷에서 발생할 때 알림을 보내도록 버킷을 구성합니다.

```
static void processS3Events(String bucketName, String queueArn) {
    // Configure the bucket to send Object Created and Object Tagging notifications to an existing SQS queue.
    s3Client.putBucketNotificationConfiguration(b -> b
            .notificationConfiguration(ncb -> ncb
                    .queueConfigurations(qcb -> qcb
                            .events(Event.S3_OBJECT_CREATED, Event.S3_OBJECT_TAGGING)
                            .queueArn(queueArn)))
                    .bucket(bucketName)
    );
}
```

위에 표시된 코드는 2가지 유형의 이벤트를 수신하도록 하나의 대기열을 설정합니다. 편리하게 `queueConfigurations` 메서드를 사용하면 필요한 경우 여러 대기열 대상을 설정할 수 있습니다. 또한 `notificationConfiguration` 메서드에서 하나 이상의 Amazon SNS 주제 또는 하나 이상의 Lambda 함수와 같은 추가 대상을 설정할 수 있습니다. 다음 코드 조각은 대기열 2개와 대상 유형 3개가 있는 예제를 보여줍니다.

```
s3Client.putBucketNotificationConfiguration(b -> b
                .notificationConfiguration(ncb -> ncb
                        .queueConfigurations(qcb -> qcb
                                .events(Event.S3_OBJECT_CREATED, Event.S3_OBJECT_TAGGING)
                                .queueArn(queueArn), 
                                qcb2 -> qcb2.<...>)
                        .topicConfigurations(tcb -> tcb.<...>)
                        .lambdaFunctionConfigurations(lfcb -> lfcb.<...>))
                        .bucket(bucketName)
        );
```

코드 예제 GitHub 리포지토리에는 S3 이벤트 알림을 대기열로 직접 전송하는 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/ProcessS3EventNotification.java)가 포함되어 있습니다.

## EventBridge로 전송할 버킷 구성
<a name="s3-event-conf-bucket-eventbridge"></a>

다음 예시에서는 EventBridge에 알림을 보내도록 버킷을 구성합니다.

```
public static String setBucketNotificationToEventBridge(String bucketName) {
    // Enable bucket to emit S3 Event notifications to EventBridge.
    s3Client.putBucketNotificationConfiguration(b -> b
            .bucket(bucketName)
            .notificationConfiguration(b1 -> b1
                    .eventBridgeConfiguration(SdkBuilder::build))
    .build());
```

EventBridge로 이벤트를 보내도록 버킷을 구성할 때 EventBridge가 디스패치할 이벤트 유형이나 최종 대상이 아닌 EventBridge 대상을 나타내기만 하면 됩니다. Java SDK의 EventBridge 클라이언트를 사용하여 최종 대상 및 이벤트 유형을 구성합니다.

다음 코드는 *객체 만들기* 이벤트를 주제 및 대기열로 팬아웃하도록 EventBridge를 구성하는 방법을 보여줍니다.

```
   public static String configureEventBridge(String topicArn, String queueArn) {
        try {
            // Create an EventBridge rule to route Object Created notifications.
            PutRuleRequest putRuleRequest = PutRuleRequest.builder()
                    .name(RULE_NAME)
                    .eventPattern("""
                            {
                              "source": ["aws.s3"],
                              "detail-type": ["Object Created"],
                              "detail": {
                                "bucket": {
                                  "name": ["%s"]
                                }
                              }
                            }
                            """.formatted(bucketName))
                    .build();

            // Add the rule to the default event bus.
            PutRuleResponse putRuleResponse = eventBridgeClient.putRule(putRuleRequest)
                    .whenComplete((r, t) -> {
                        if (t != null) {
                            logger.error("Error creating event bus rule: " + t.getMessage(), t);
                            throw new RuntimeException(t.getCause().getMessage(), t);
                        }
                        logger.info("Event bus rule creation request sent successfully. ARN is: {}", r.ruleArn());
                    }).join();

            // Add the existing SNS topic and SQS queue as targets to the rule.
            eventBridgeClient.putTargets(b -> b
                    .eventBusName("default")
                    .rule(RULE_NAME)
                    .targets(List.of (
                            Target.builder()
                                    .arn(queueArn)
                                    .id("Queue")
                                    .build(),
                            Target.builder()
                                    .arn(topicArn)
                                    .id("Topic")
                                    .build())
                            )
                    ).join();
            return putRuleResponse.ruleArn();
        } catch (S3Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return null;
    }
```

Java 코드에서 EventBridge로 작업하려면 Maven `pom.xml` 파일에 `eventbridge` 아티팩트에 대한 종속성을 추가합니다.

```
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>eventbridge</artifactId>
</dependency>
```

코드 예제 GitHub 리포지토리에는 S3 이벤트 알림을 EventBridge로 전송한 다음 주제 및 대기열로 전송하는 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/PutBucketS3EventNotificationEventBridge.java)가 포함되어 있습니다.

## S3 이벤트 알림 API를 사용하여 이벤트 처리
<a name="s3-event-notification-read"></a>

대상이 S3 알림 이벤트를 수신한 후 S3 이벤트 알림 API를 사용하여 객체 중심 방식으로 이를 처리할 수 있습니다. S3 이벤트 알림 API를 사용하여 대상으로 직접 디스패치되는 이벤트 알림([첫 번째 예제](#s3-event-conf-bucket-direct) 참조)을 사용할 수 있지만 EventBridge를 통해 라우팅되는 알림은 사용할 수 없습니다. 버킷에서 EventBridge로 전송된 S3 이벤트 알림에는 S3 이벤트 알림 API가 현재 처리하지 않는 [다른 구조](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html#ev-events-list)가 포함되어 있습니다.

### 종속성 추가
<a name="s3-event-notifications-dep"></a>

S3 이벤트 알림 API는 SDK for Java 2.x의 버전 2.25.11과 함께 릴리스되었습니다.

S3 이벤트 알림 API를 사용하려면 다음 코드 조각과 같이 Maven `pom.xml`에 필요한 종속성 요소를 추가합니다.

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.X.X1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-event-notifications</artifactId>
    </dependency>
</dependencies>
```

1 [최신 버전](https://central.sonatype.com/artifact/software.amazon.awssdk/bom).

### `S3EventNotification` 클래스 사용
<a name="s3-event-notifications-use"></a>

#### JSON 문자열에서 `S3EventNotification` 인스턴스 만들기
<a name="s3-event-notifications-use-from-json"></a>

JSON 문자열을 `S3EventNotification` 객체로 변환하려면 다음 예제와 같이 `S3EventNotification` 클래스의 정적 메서드를 사용합니다.

```
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotificationRecord
import software.amazon.awssdk.services.sqs.model.Message; 

public class S3EventNotificationExample {
    ...
    
    void receiveMessage(Message message) {
       // Message received from SQSClient.
       String sqsEventBody = message.body();
       S3EventNotification s3EventNotification = S3EventNotification.fromJson(sqsEventBody);
    
       // Use getRecords() to access all the records in the notification.                                                                                                       
       List<S3EventNotificationRecord> records = s3EventNotification.getRecords();   
    
        S3EventNotificationRecord record = records.stream().findFirst();
        // Use getters on the record to access individual attributes.
        String awsRegion = record.getAwsRegion();
        String eventName = record.getEventName();
        String eventSource = record.getEventSource();                                                                                                   
    }
}
```

이 예제에서 `fromJson` 메서드는 JSON 문자열을 `S3EventNotification` 객체로 변환합니다. JSON 문자열에 누락된 필드가 있으면 해당 Java 객체 필드에 `null` 값이 표시되고 JSON의 추가 필드는 무시됩니다.

이벤트 알림 레코드에 대한 다른 API는 `[S3EventNotificationRecord](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/eventnotifications/s3/model/S3EventNotificationRecord.html)`에 대한 API 참조에서 확인할 수 있습니다.

#### `S3EventNotification` 인스턴스를 JSON 문자열로 변환
<a name="s3-event-notifications-use-to-json"></a>

다음 예제와 같이 `toJson`(또는 `toJsonPretty`) 메서드를 사용하여 `S3EventNotification` 객체를 JSON 문자열로 변환합니다.

```
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification

public class S3EventNotificationExample {
    ...

    void toJsonString(S3EventNotification event) {

        String json = event.toJson();
        String jsonPretty = event.toJsonPretty();

        System.out.println("JSON: " + json);
        System.out.println("Pretty JSON: " + jsonPretty);
    }
}
```

`GlacierEventData`, `ReplicationEventData`, `IntelligentTieringEventData`, `LifecycleEventData`에 대한 필드는 `null`인 경우 JSON에서 제외됩니다. 다른 `null` 필드는 `null`로 직렬화됩니다.

다음은 S3 객체 태그 지정 이벤트에 대한 `toJsonPretty` 메서드의 출력 예제입니다.

```
{
  "Records" : [ {
    "eventVersion" : "2.3",
    "eventSource" : "aws:s3",
    "awsRegion" : "us-east-1",
    "eventTime" : "2024-07-19T20:09:18.551Z",
    "eventName" : "ObjectTagging:Put",
    "userIdentity" : {
      "principalId" : "AWS:XXXXXXXXXXX"
    },
    "requestParameters" : {
      "sourceIPAddress" : "XXX.XX.XX.XX"
    },
    "responseElements" : {
      "x-amz-request-id" : "XXXXXXXXXXXX",
      "x-amz-id-2" : "XXXXXXXXXXXXX"
    },
    "s3" : {
      "s3SchemaVersion" : "1.0",
      "configurationId" : "XXXXXXXXXXXXX",
      "bucket" : {
        "name" : "amzn-s3-demo-bucket",
        "ownerIdentity" : {
          "principalId" : "XXXXXXXXXXX"
        },
        "arn" : "arn:aws:s3:::XXXXXXXXXX"
      },
      "object" : {
        "key" : "akey",
        "size" : null,
        "eTag" : "XXXXXXXXXX",
        "versionId" : null,
        "sequencer" : null
      }
    }
  } ]
}
```

API를 사용하여 Amazon SQS 대기열에서 수신한 알림으로 작업하는 방법을 보여주는 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/75c3daadf750406156fc87fa30ee499a206b4a36/javav2/example_code/s3/src/main/java/com/example/s3/ProcessS3EventNotification.java#L117)는 GitHub에서 사용할 수 있습니다.

## Java 라이브러리를 사용하여 Lambda에서 S3 이벤트 처리: AWS SDK for Java 2.x 및 `aws-lambda-java-events`
<a name="s3-event-notif-processing-options"></a>

Java 2.x용 SDK를 사용하여 Lambda 함수에서 Amazon S3 이벤트 알림을 처리하는 대신 `[aws-lambda-java-events](https://github.com/aws/aws-lambda-java-libs/tree/main/aws-lambda-java-events)` 버전 3.x.x. AWS 의 `aws-lambda-java-events` 라이브러리를 독립적으로 사용할 수 있으며 자체 종속성 요구 사항이 있습니다. `aws-lambda-java-events` 라이브러리는 Lambda 함수의 S3 이벤트에서만 작동하는 반면, SDK for Java 2.x는 Lambda 함수, Amazon SNS 및 Amazon SQS의 S3 이벤트에서 작동합니다.

두 접근 방식 모두 유사한 API를 사용하여 객체 중심 방식으로 JSON 이벤트 알림 페이로드를 모델링합니다. 다음 표에는 두 접근 방식을 사용하는 데 있어 주목할 만한 차이점이 나와 있습니다.


****  

|  | AWS SDK for Java | aws-lambda-java-events 라이브러리 | 
| --- | --- | --- | 
| 패키지 이름 지정 |  `software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification`  | com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification | 
| RequestHandler 파라미터 |  Lambda 함수의 `RequestHandler` 구현을 작성하여 JSON 문자열을 수신합니다. <pre>import com.amazonaws.services.lambda.runtime.Context;<br />import com.amazonaws.services.lambda.runtime.RequestHandler;<br />import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification;<br /><br />public class Handler implements RequestHandler<String, String> {<br /><br />    @Override<br />        public String handleRequest(String jsonS3Event, Context context) {<br />            S3EventNotification s3Event = S3EventNotification<br />                                             .fromJson(jsonS3Event);<br />            // Work with the s3Event object.        <br />            ...<br />    }<br />}</pre>  | Lambda 함수의 RequestHandler 구현을 작성하여 S3Event 객체를 수신합니다.<pre>import com.amazonaws.services.lambda.runtime.Context;<br />import com.amazonaws.services.lambda.runtime.RequestHandler;<br />import com.amazonaws.services.lambda.runtime.events.S3Event;<br /><br />public class Handler implements RequestHandler<S3Event, String> {<br /><br />    @Override<br />        public String handleRequest(S3Event s3event, Context context) {<br />            // Work with the s3Event object.        <br />            ...<br />    }<br />}</pre> | 
| Maven 종속성 |  <pre><dependencyManagement><br />    <dependencies><br />        <dependency><br />            <groupId>software.amazon.awssdk</groupId><br />            <artifactId>bom</artifactId><br />            <version>2.X.X</version><br />            <type>pom</type><br />            <scope>import</scope><br />        </dependency><br />    </dependencies><br /></dependencyManagement><br /><dependencies><br />    <dependency><br />        <groupId>software.amazon.awssdk</groupId><br />        <artifactId>s3-event-notifications</artifactId><br />    </dependency><br />    <!-- Add other SDK dependencies that you need. --><br /></dependencies></pre>  |  <pre><dependencyManagement><br />    <dependencies><br />        <dependency><br />            <groupId>software.amazon.awssdk</groupId><br />            <artifactId>bom</artifactId><br />            <version>2.X.X</version><br />            <type>pom</type><br />            <scope>import</scope><br />        </dependency><br />    </dependencies><br /></dependencyManagement><br /><dependencies><br />    <!-- The following two dependencies are for the <br />         aws-lambda-java-events library. --><br />    <dependency><br />        <groupId>com.amazonaws</groupId><br />        <artifactId>aws-lambda-java-core</artifactId><br />        <version>1.2.3</version>     <br />    </dependency><br />    <dependency><br />        <groupId>com.amazonaws</groupId><br />        <artifactId>aws-lambda-java-events</artifactId><br />        <version>3.15.0</version><br />    </dependency><br />    <!-- Add other SDK dependencies that you need. --><br /></dependencies></pre>  | 

# Amazon Simple Notification Service 작업
<a name="examples-simple-notification-service"></a>

Amazon Simple Notification Service를 사용하면 여러 통신 채널을 통해 애플리케이션의 실시간 알림 메시지를 구독자에게 쉽게 푸시할 수 있습니다. 이 항목에서는 Amazon SNS의 기본 기능을 수행하는 방법을 설명합니다.

## 주제 생성
<a name="sns-create-topic"></a>

**주제**는 메시지를 전송할 시스템을 정의하는 통신 채널의 논리적 그룹화입니다(예: 메시지를 AWS Lambda 및 HTTP Webhook에 팬 아웃). 메시지를 Amazon SNS에 전송하면 메시지는 주제에 정의된 채널로 배포됩니다. 이렇게 하면 구독자가 메시지를 사용할 수 있게 됩니다.

주제를 생성하려면 먼저 빌더의 `name()` 메서드를 사용하여 세트 이름을 설정하여 [CreateTopicRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/CreateTopicRequest.html) 객체를 빌드합니다. 그런 다음 [SnsClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/SnsClient.html)의 `createTopic()` 메서드를 사용하여 요청 객체를 Amazon SNS에 전송합니다. 다음 코드 조각과 같이, 이 요청의 결과를 [CreateTopicResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/CreateTopicResponse.html) 객체로 캡처할 수 있습니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.CreateTopicRequest;
import software.amazon.awssdk.services.sns.model.CreateTopicResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 ** 코드** 

```
    public static String createSNSTopic(SnsClient snsClient, String topicName ) {

        CreateTopicResponse result = null;
        try {
            CreateTopicRequest request = CreateTopicRequest.builder()
                    .name(topicName)
                    .build();

            result = snsClient.createTopic(request);
            return result.topicArn();
        } catch (SnsException e) {

            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "";
    }
```

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

## Amazon SNS 주제 나열
<a name="sns-crelistate-topics"></a>

기존 Amazon SNS 주제의 목록을 검색하려면 [ListTopicsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/ListTopicsRequest.html) 객체를 빌드합니다. 그런 다음 Amazon SNS의 `listTopics()` 메서드를 사용하여 요청 객체를 `SnsClient`에 전송합니다. 이 요청의 결과를 [ListTopicsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/ListTopicsResponse.html) 객체로 캡처할 수 있습니다.

다음 코드 조각은 요청의 HTTP 상태 코드와 Amazon SNS 주제의 Amazon 리소스 이름(ARN) 목록을 인쇄합니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.ListTopicsRequest;
import software.amazon.awssdk.services.sns.model.ListTopicsResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 ** 코드** 

```
    public static void listSNSTopics(SnsClient snsClient) {

        try {
            ListTopicsRequest request = ListTopicsRequest.builder()
                   .build();

            ListTopicsResponse result = snsClient.listTopics(request);
            System.out.println("Status was " + result.sdkHttpResponse().statusCode() + "\n\nTopics\n\n" + result.topics());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 주제에 엔드포인트 구독 설정
<a name="sns-subscribe-endpoint-topic"></a>

주제를 생성한 후 해당 주제의 엔드포인트가 될 통신 채널을 구성할 수 있습니다. Amazon SNS가 메시지를 수신한 후 메시지는 이러한 엔드포인트에 배포됩니다.

통신 채널을 주제의 엔드포인트로 구성하려면 해당 엔드포인트가 주제에 구독하도록 설정합니다. 시작하려면 [SubscribeRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/SubscribeRequest.html) 객체를 빌드합니다. 통신 채널(예: `lambda` 또는 `email`)을 `protocol()`로 지정합니다. `endpoint()`을 관련 출력 위치(예: Lambda 함수의 ARN 또는 이메일 주소)로 설정한 다음, 구독하려는 주제의 ARN을 `topicArn()`으로 설정합니다. 그런 다음 `SnsClient`의 `subscribe()` 메서드를 사용하여 요청 객체를 Amazon SNS에 전송합니다. 이 요청의 결과를 [SubscribeResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/SubscribeResponse.html) 객체로 캡처할 수 있습니다.

다음 코드 조각은 전자 메일 주소가 주제를 구독하도록 설정하는 방법을 보여 줍니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.SnsException;
import software.amazon.awssdk.services.sns.model.SubscribeRequest;
import software.amazon.awssdk.services.sns.model.SubscribeResponse;
```

 ** 코드** 

```
    public static void subEmail(SnsClient snsClient, String topicArn, String email) {

        try {
            SubscribeRequest request = SubscribeRequest.builder()
                .protocol("email")
                .endpoint(email)
                .returnSubscriptionArn(true)
                .topicArn(topicArn)
                .build();

            SubscribeResponse result = snsClient.subscribe(request);
            System.out.println("Subscription ARN: " + result.subscriptionArn() + "\n\n Status is " + result.sdkHttpResponse().statusCode());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 주제에 메시지 게시
<a name="sns-publish-message-topic"></a>

주제를 생성하고 주제에 대해 하나 이상의 엔드포인트를 구성한 후에는 메시지를 주제에 게시할 수 있습니다. 시작하려면 [PublishRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/PublishRequest.html) 객체를 빌드합니다. 전송할 `message()`와 메시지를 전송할 주제의 ARN(`topicArn()`)을 지정합니다. 그런 다음 Amazon SNS의 `publish()` 메서드를 사용하여 요청 객체를 `SnsClient`에 전송합니다. 이 요청의 결과를 [PublishResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/PublishResponse.html) 객체로 캡처할 수 있습니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.PublishRequest;
import software.amazon.awssdk.services.sns.model.PublishResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 ** 코드** 

```
    public static void pubTopic(SnsClient snsClient, String message, String topicArn) {

        try {
            PublishRequest request = PublishRequest.builder()
                .message(message)
                .topicArn(topicArn)
                .build();

            PublishResponse result = snsClient.publish(request);
            System.out.println(result.messageId() + " Message sent. Status is " + result.sdkHttpResponse().statusCode());

         } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
         }
    }
```

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

## 주제에서 엔드포인트 구독 취소
<a name="sns-unsubscribe-endpoint-topic"></a>

주제의 엔드포인트로 구성된 통신 채널을 제거할 수 있습니다. 이렇게 한 후에도 주제 자체는 계속 존재하며 해당 주제에 대해 구성된 다른 엔드포인트에 메시지를 배포합니다.

주제의 엔드포인트로 구성된 통신 채널을 제거하려면 주제에서 해당 엔드포인트의 구독을 해지합니다. 시작하려면 [UnsubscribeRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/UnsubscribeRequest.html) 객체를 빌드하고 구독 해지할 주제의 ARN을 `subscriptionArn()`으로 설정합니다. 그런 다음 `unsubscribe()`의 `SnsClient` 메서드를 사용하여 요청 객체를 SNS에 전송합니다. 이 요청의 결과를 [UnsubscribeResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/UnsubscribeResponse.html) 객체로 캡처할 수 있습니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.SnsException;
import software.amazon.awssdk.services.sns.model.UnsubscribeRequest;
import software.amazon.awssdk.services.sns.model.UnsubscribeResponse;
```

 ** 코드** 

```
    public static void unSub(SnsClient snsClient, String subscriptionArn) {

        try {
            UnsubscribeRequest request = UnsubscribeRequest.builder()
                .subscriptionArn(subscriptionArn)
                .build();

            UnsubscribeResponse result = snsClient.unsubscribe(request);

            System.out.println("\n\nStatus was " + result.sdkHttpResponse().statusCode()
                + "\n\nSubscription was removed for " + request.subscriptionArn());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

## 주제 삭제
<a name="sns-delete-topic"></a>

Amazon SNS 주제를 삭제하려면 먼저 빌더에서 주제 세트의 ARN을 `topicArn()` 메서드로 사용하여 [DeleteTopicRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/DeleteTopicRequest.html) 객체를 빌드합니다. 그런 다음 Amazon SNS의 `deleteTopic()` 메서드를 사용하여 요청 객체를 `SnsClient`에 전송합니다. 다음 코드 조각과 같이, 이 요청의 결과를 [DeleteTopicResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/DeleteTopicResponse.html) 객체로 캡처할 수 있습니다.

 **가져옵니다**.

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.DeleteTopicRequest;
import software.amazon.awssdk.services.sns.model.DeleteTopicResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 ** 코드** 

```
    public static void deleteSNSTopic(SnsClient snsClient, String topicArn ) {

        try {
            DeleteTopicRequest request = DeleteTopicRequest.builder()
                .topicArn(topicArn)
                .build();

            DeleteTopicResponse result = snsClient.deleteTopic(request);
            System.out.println("\n\nStatus was " + result.sdkHttpResponse().statusCode());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

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

자세한 내용은 [Amazon Simple Notification Service 개발자 안내서](https://docs.aws.amazon.com/sns/latest/dg/)를 참조하세요.

# Amazon Simple Queue Service 작업
<a name="examples-sqs"></a>

이 단원에서는 AWS SDK for Java 2.x를 사용한 [Amazon Simple Queue Service](https://docs.aws.amazon.com/sqs/) 프로그래밍의 예제를 제공합니다.

다음 예제에는 각 기술을 보여주는 데 필요한 코드만 포함되어 있습니다. [전체 예제 코드는 GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)에 있습니다. 이 위치에서 단일 소스 파일을 다운로드하거나 리포지토리를 로컬로 복사하여 모든 예제를 빌드하고 실행할 수 있습니다.

**Topics**
+ [자동 요청 배치 처리 사용](sqs-auto-batch.md)
+ [대기열 작업](examples-sqs-message-queues.md)
+ [메시지 작업](examples-sqs-messages.md)

# 에서 Amazon SQS에 자동 요청 일괄 처리 사용 AWS SDK for Java 2.x
<a name="sqs-auto-batch"></a>

Amazon SQS용 Automatic Request Batching API는 SQS 작업에 대한 요청을 배치 처리 및 버퍼링하는 효율적인 방법을 제공하는 개괄적인 라이브러리입니다. 배치 처리 API를 사용하면 SQS에 대한 요청 수를 줄여 처리량을 개선하고 비용을 최소화할 수 있습니다.

배치 API 메서드는 `[SqsAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsAsyncClient.html)` 메서드(`sendMessage`, `changeMessageVisibility`, `deleteMessage`, `receiveMessage`)와 일치하므로 최소한의 변경 사항으로 배치 API를 드롭인 대체로 사용할 수 있습니다.

이 주제에서는 Amazon SQS용 Automatic Request Batching API를 구성하고 사용하는 방법에 대한 개요를 제공합니다.

## 사전 조건 확인
<a name="sqs-auto-batch-requirements"></a>

배치 처리 API에 액세스하려면 SDK for Java 2.x의 버전 *2.28.0 *이상을 사용해야 합니다. Maven `pom.xml`에는 최소한 다음 요소가 포함되어야 합니다.

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.28.231</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>sqs</artifactId>
    </dependency>
</dependencies>
```

1 [최신 버전](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)

## 배치 관리자 만들기
<a name="sqs-auto-batch-create"></a>

자동 요청 배치 처리 API는 [SqsAsyncBatchManager](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html) 인터페이스에 따라 구현됩니다. 몇 가지 방법으로 관리자의 인스턴스를 만들 수 있습니다.

### `SqsAsyncClient`를 사용한 기본 구성
<a name="sqs-batch-manager-create-default"></a>

배치 관리자를 만드는 가장 간단한 방법은 기존 [SqsAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsAsyncClient.html) 인스턴스에서 `batchManager` 팩토리 메서드를 직접적으로 호출하는 것입니다. 이 접근 방법은 다음 코드 조각에 나와 있습니다.

```
SqsAsyncClient asyncClient = SqsAsyncClient.create();
SqsAsyncBatchManager sqsAsyncBatchManager = asyncClient.batchManager();
```

이 접근 방식을 사용할 경우 `SqsAsyncBatchManager` 인스턴스는 [`SqsAsyncBatchManager`에 대한 구성 설정 재정의](#sqs-auto-batch-config-settings) 섹션의 표에 표시된 기본값을 사용합니다. 또한 `SqsAsyncBatchManager` 인스턴스는 만들어진 `SqsAsyncClient` 인스턴스의 `ExecutorService`를 사용합니다.

### `SqsAsyncBatchManager.Builder`를 사용한 사용자 지정 구성
<a name="sqs-batch-manager-create-custom"></a>

고급 사용 사례의 경우 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.Builder.html)를 사용하여 배치 관리자를 사용자 지정할 수 있습니다. 이 접근 방식을 사용하여 `SqsAsyncBatchManager` 인스턴스를 만들면 배치 처리 동작을 미세 조정할 수 있습니다. 다음 코드 조각은 빌더를 사용하여 배치 처리 동작을 사용자 지정하는 방법의 예를 보여줍니다.

```
SqsAsyncBatchManager batchManager = SqsAsyncBatchManager.builder()
    .client(SqsAsyncClient.create())
    .scheduledExecutor(Executors.newScheduledThreadPool(5))
    .overrideConfiguration(b -> b
        .receiveMessageMinWaitDuration(Duration.ofSeconds(10))
        .receiveMessageVisibilityTimeout(Duration.ofSeconds(1))
        .receiveMessageAttributeNames(Collections.singletonList("*"))
        .receiveMessageSystemAttributeNames(Collections.singletonList(MessageSystemAttributeName.ALL)))
    .build();
```

이 접근 방식을 사용하면 [`SqsAsyncBatchManager`에 대한 구성 설정 재정의](#sqs-auto-batch-config-settings) 섹션의 표에 표시된 `BatchOverrideConfiguration` 객체의 설정을 조정할 수 있습니다. 이 접근 방식을 사용하여 배치 관리자에 사용자 지정 [https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html)를 제공할 수도 있습니다.

## 메시지 전송
<a name="sqs-auto-batch-send"></a>

배치 관리자로 메시지를 보내려면 `[SqsAsyncBatchManager\$1sendMessage](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#sendMessage(software.amazon.awssdk.services.sqs.model.SendMessageRequest))` 메서드를 사용합니다. SDK는 요청을 버퍼링하고 `maxBatchSize` 또는 `sendRequestFrequency` 값에 도달하면 요청을 배치로 보냅니다.

다음 예제에서는 다른 요청 바로 다음에 오는 `sendMessage` 요청을 보여줍니다. 이 경우 SDK는 두 메시지를 단일 배치로 전송합니다.

```
// Sending the first message
CompletableFuture<SendMessageResponse> futureOne = 
    sqsAsyncBatchManager.sendMessage(r -> r.messageBody("One").queueUrl("queue"));

// Sending the second message
CompletableFuture<SendMessageResponse> futureTwo = 
    sqsAsyncBatchManager.sendMessage(r -> r.messageBody("Two").queueUrl("queue"));

// Waiting for both futures to complete and retrieving the responses
SendMessageResponse messageOne = futureOne.join();
SendMessageResponse messageTwo = futureTwo.join();
```

## 메시지 표시 제한 시간 변경
<a name="sqs-auto-batch-change-vis"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#changeMessageVisibility(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#changeMessageVisibility(java.util.function.Consumer)) 메서드를 사용하여 배치 단위로 메시지의 표시 제한 시간을 변경할 수 있습니다. SDK는 요청을 버퍼링하고 `maxBatchSize` 또는 `sendRequestFrequency` 값에 도달하면 요청을 배치로 보냅니다.

다음 예제에는 `changeMessageVisibility` 메서드를 직접적으로 호출하는 방법이 나와 있습니다.

```
CompletableFuture<ChangeMessageVisibilityResponse> futureOne =
    sqsAsyncBatchManager.changeMessageVisibility(r -> 
        r.receiptHandle("receiptHandle")
         .queueUrl("queue"));
ChangeMessageVisibilityResponse response = futureOne.join();
```

## 메시지 삭제
<a name="sqs-auto-batch-delete"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#deleteMessage(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#deleteMessage(java.util.function.Consumer)) 메서드를 사용하여 배치 단위로 메시지를 삭제할 수 있습니다. SDK는 요청을 버퍼링하고 `maxBatchSize` 또는 `sendRequestFrequency` 값에 도달하면 요청을 배치로 보냅니다.

다음 예제에서는 `deleteMessage` 메서드를 직접적으로 호출하는 방법을 보여줍니다.

```
CompletableFuture<DeleteMessageResponse> futureOne = 
    sqsAsyncBatchManager.deleteMessage(r -> 
        r.receiptHandle("receiptHandle")
         .queueUrl("queue"));
DeleteMessageResponse response = futureOne.join();
```

## 메시지 수신
<a name="sqs-auto-batch-receive"></a>

### 기본 설정 사용
<a name="sqs-auto-batch-receive-default-settings"></a>

애플리케이션에서 `[SqsAsyncBatchManager\$1receiveMessage](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#receiveMessage(java.util.function.Consumer))` 메서드를 폴링하면 배치 관리자가 내부 버퍼에서 메시지를 가져와 SDK가 백그라운드에서 자동으로 업데이트됩니다.

다음 예제에는 `receiveMessage` 메서드를 직접적으로 호출하는 방법이 나와 있습니다.

```
CompletableFuture<ReceiveMessageResponse> responseFuture = 
    sqsAsyncBatchManager.receiveMessage(r -> r.queueUrl("queueUrl"));
```

### 사용자 지정 설정 사용
<a name="sqs-auto-batch-receive-custom-settings"></a>

예를 들어 사용자 지정 대기 시간을 설정하고 검색할 메시지 수를 지정하여 요청을 추가로 사용자 지정하려면 다음 예제와 같이 요청을 사용자 지정할 수 있습니다.

```
CompletableFuture<ReceiveMessageResponse> response = 
    sqsAsyncBatchManager.receiveMessage(r -> 
        r.queueUrl("queueUrl")
         .waitTimeSeconds(5)
         .visibilityTimeout(20));
```

**참고**  
다음 파라미터가 포함된 `[ReceiveMessageRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ReceiveMessageRequest.html)`를 사용하여 `receiveMessage`를 직접적으로 호출하면 SDK는 배치 관리자를 우회하고 일반 비동기식 `receiveMessage` 요청을 보냅니다.  
`messageAttributeNames`
`messageSystemAttributeNames`
`messageSystemAttributeNamesWithStrings`
`overrideConfiguration`

## `SqsAsyncBatchManager`에 대한 구성 설정 재정의
<a name="sqs-auto-batch-config-settings"></a>

`SqsAsyncBatchManager` 인스턴스를 만들 때 다음 설정을 조정할 수 있습니다. 다음 설정 목록을 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/BatchOverrideConfiguration.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/BatchOverrideConfiguration.Builder.html)에서 사용할 수 있습니다.


| 설정 | 설명 | 기본값  | 
| --- | --- | --- | 
| maxBatchSize | 각 SendMessageBatchRequest, ChangeMessageVisibilityBatchRequest 또는 DeleteMessageBatchRequest에 대한 배치당 최대 요청 수입니다. 최대값은 10입니다. | 10 | 
| sendRequestFrequency |  배치를 보내기 전까지의 시간이며, `maxBatchSize`에 도달하면 앞서 전송됩니다. 값이 높을수록 요청이 줄어들지만 지연 시간은 늘어날 수 있습니다.  | 200ms | 
| receiveMessageVisibilityTimeout | 메시지에 대한 표시 제한 시간입니다. 설정되지 않으면 대기열의 기본값이 사용됩니다. | 대기열의 기본값 | 
| receiveMessageMinWaitDuration | receiveMessage 요청의 최소 대기 시간입니다. CPU 낭비를 방지하기 위해 0으로 설정하는 것은 피합니다. | 50ms | 
| receiveMessageSystemAttributeNames | receiveMessage 직접 호출을 요청할 [시스템 속성 이름](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/MessageSystemAttributeName.html) 목록입니다. | 없음 | 
| receiveMessageAttributeNames | receiveMessage 직접 호출을 요청할 [속성 이름](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes) 목록입니다. | 없음 | 

# Amazon Simple Queue Service 메시지 대기열 작업
<a name="examples-sqs-message-queues"></a>

*메시지 대기열*은 메시지를 안정적으로 보내는 데 사용되는 논리적 컨테이너입니다 Amazon Simple Queue Service. *표준*과 *선입선출*(FIFO), 이렇게 두 가지 유형의 대기열이 있습니다. 대기열과 이러한 유형 간의 차이에 대해 자세히 알아보려면 [Amazon Simple Queue Service 개발자 안내서](https://docs.aws.amazon.com//AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html)를 참조하세요.

이 주제에서는를 사용하여 Amazon Simple Queue Service 대기열의 URL을 생성, 나열, 삭제 및 가져오는 방법을 설명합니다 AWS SDK for Java.

다음 예제에서 사용되는 `sqsClient` 변수는 다음 코드 조각에서 만들 수 있습니다.

```
SqsClient sqsClient = SqsClient.create();
```

정적 `create()` 메서드를 사용하여 `SqsClient`를 만들면 SDK가 [기본 리전 공급자 체인](region-selection.md#default-region-provider-chain)을 사용하여 리전을 구성하고 [기본 자격 증명 공급자 체인](credentials-chain.md)을 사용하여 자격 증명을 구성합니다.

## 대기열 생성
<a name="sqs-create-queue"></a>

`SqsClient’s` `createQueue` 메서드를 사용하고 다음 코드 조각과 같이 대기열 파라미터를 설명하는 `[CreateQueueRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/CreateQueueRequest.html)` 객체를 제공합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
            CreateQueueRequest createQueueRequest = CreateQueueRequest.builder()
                .queueName(queueName)
                .build();

            sqsClient.createQueue(createQueueRequest);
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L52)를 참조하십시오.

## 대기열 나열
<a name="sqs-list-queues"></a>

계정의 Amazon Simple Queue Service 대기열을 나열하려면 `[ListQueuesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ListQueuesRequest.html)` 객체를 사용하여 `SqsClient’s` `listQueues` 메서드를 호출합니다.

파라미터를 사용하지 않는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#listQueues()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#listQueues()) 메서드 형식을 사용하면 서비스에서는 최대 1,000개의 대기열까지 *모든 대기열*을 반환합니다.

`[ListQueuesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ListQueuesRequest.html)` 객체에 대기열 이름 접두사를 지정해 다음 코드와 같이 접두사와 일치하는 대기열만 결과로 표시되도록 제한할 수 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
        String prefix = "que";

        try {
            ListQueuesRequest listQueuesRequest = ListQueuesRequest.builder().queueNamePrefix(prefix).build();
            ListQueuesResponse listQueuesResponse = sqsClient.listQueues(listQueuesRequest);

            for (String url : listQueuesResponse.queueUrls()) {
                System.out.println(url);
            }

        } catch (SqsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L79)를 참조하십시오.

## 대기열의 URL 가져오기
<a name="sqs-get-queue-url"></a>

다음 코드는 `[GetQueueUrlRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/GetQueueUrlRequest.html)` 객체로 `SqsClient’s` `getQueueUrl` 메서드를 직접적으로 호출하여 대기열의 URL을 가져오는 방법을 보여줍니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
            GetQueueUrlResponse getQueueUrlResponse =
                sqsClient.getQueueUrl(GetQueueUrlRequest.builder().queueName(queueName).build());
            String queueUrl = getQueueUrlResponse.queueUrl();
            return queueUrl;
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/7486a1a092aa8e16a21698ef26f9d524fef62e55/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L70)를 참조하십시오.

## 대기열 삭제
<a name="sqs-delete-queue"></a>

대기열의 [URL](#sqs-get-queue-url)을 `[DeleteQueueRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/DeleteQueueRequest.html)` 객체에 제공합니다. 그런 다음, 다음 코드와 같이 `SqsClient’s` `deleteQueue` 메서드를 직접적으로 호출하여 대기열을 삭제합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
    public static void deleteSQSQueue(SqsClient sqsClient, String queueName) {

        try {

            GetQueueUrlRequest getQueueRequest = GetQueueUrlRequest.builder()
                    .queueName(queueName)
                    .build();

            String queueUrl = sqsClient.getQueueUrl(getQueueRequest).queueUrl();

            DeleteQueueRequest deleteQueueRequest = DeleteQueueRequest.builder()
                    .queueUrl(queueUrl)
                    .build();

            sqsClient.deleteQueue(deleteQueueRequest);

        } catch (SqsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/6240df86c5f17eae1e23d1139d1435c7dc4b2a11/javav2/example_code/sqs/src/main/java/com/example/sqs/DeleteQueue.java#L48)를 참조하십시오.

## 추가 정보
<a name="more-information"></a>
+  Amazon Simple Queue Service API 참조의 [CreateQueue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html) 
+  Amazon Simple Queue Service API 참조의 [GetQueueUrl](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueUrl.html) 
+  Amazon Simple Queue Service API 참조의 [ListQueues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ListQueues.html) 
+  Amazon Simple Queue Service API 참조의 [DeleteQueue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteQueue.html) 

# Amazon Simple Queue Service 메시지 전송, 수신 및 삭제
<a name="examples-sqs-messages"></a>

메시지는 분산된 구성 요소가 송신 및 수신할 수 있는 데이터 조각입니다. 메시지는 항상 [SQS 대기열](examples-sqs-message-queues.md)을 사용하여 전달됩니다.

다음 예제에서 사용되는 `sqsClient` 변수는 다음 코드 조각에서 만들 수 있습니다.

```
SqsClient sqsClient = SqsClient.create();
```

정적 `create()` 메서드를 사용하여 `SqsClient`를 만들면 SDK가 [기본 리전 공급자 체인](region-selection.md#default-region-provider-chain)을 사용하여 리전을 구성하고 [기본 자격 증명 공급자 체인](credentials-chain.md)을 사용하여 자격 증명을 구성합니다.

## 메시지 전송
<a name="sqs-message-send"></a>

SqsClient 클라이언트 `sendMessage` 메서드를 호출하여 Amazon Simple Queue Service 대기열에 단일 메시지를 추가합니다. 대기열의 [URL](examples-sqs-message-queues.md#sqs-get-queue-url), 메시지 본문 및 선택적 지연 값(초 단위)이 포함된 [SendMessageRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/SendMessageRequest.html) 객체를 제공합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
            sqsClient.sendMessage(SendMessageRequest.builder()
                .queueUrl(queueUrl)
                .messageBody("Hello world!")
                .delaySeconds(10)
                .build());

            sqsClient.sendMessage(sendMsgRequest);
```

## 한 번의 요청에 여러 메시지를 전송
<a name="sqs-messages-send-multiple"></a>

SqsClient의 `sendMessageBatch` 메서드를 사용하여 단일 요청에서 메시지를 2개 이상 전송합니다. 이 메서드는 전송할 메시지 목록과 대기열 URL이 포함된 [SendMessageBatchRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/SendMessageBatchRequest.html)를 가져옵니다. (각 메시지는 [SendMessageBatchRequestEntry](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/SendMessageBatchRequestEntry.html)임) 또 메시지의 지연 값을 설정해 특정 메시지 전송을 지연시킬 수 있습니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
            SendMessageBatchRequest sendMessageBatchRequest = SendMessageBatchRequest.builder()
                .queueUrl(queueUrl)
                .entries(SendMessageBatchRequestEntry.builder().id("id1").messageBody("Hello from msg 1").build(),
                        SendMessageBatchRequestEntry.builder().id("id2").messageBody("msg 2").delaySeconds(10).build())
                .build();
            sqsClient.sendMessageBatch(sendMessageBatchRequest);
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L133)를 참조하십시오.

## 메시지 검색
<a name="sqs-messages-receive"></a>

SqsClient의 `receiveMessage` 메서드를 호출하여 현재 대기열에 있는 메시지를 검색합니다. 이 메서드는 대기열 URL이 포함된 [ReceiveMessageRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ReceiveMessageRequest.html)를 가져옵니다. 또 반환할 메시지의 최대 수를 지정할 수 있습니다. 메시지는 [Message](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/Message.html) 객체의 목록으로 반환됩니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
        try {
            ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.builder()
                .queueUrl(queueUrl)
                .maxNumberOfMessages(5)
                .build();
            List<Message> messages = sqsClient.receiveMessage(receiveMessageRequest).messages();
            return messages;
        } catch (SqsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return null;
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L148)를 참조하십시오.

## 수신 후 메시지 삭제
<a name="sqs-messages-delete"></a>

메시지를 수신하고 콘텐츠를 처리한 후에는 메시지의 수신 핸들과 대기열 URL을 `SqsClient's` [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#deleteMessage(software.amazon.awssdk.services.sqs.model.DeleteMessageRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#deleteMessage(software.amazon.awssdk.services.sqs.model.DeleteMessageRequest)) 메서드로 전송하여 대기열에서 메시지를 삭제합니다.

 **가져오기** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **코드** 

```
        try {
            for (Message message : messages) {
                DeleteMessageRequest deleteMessageRequest = DeleteMessageRequest.builder()
                        .queueUrl(queueUrl)
                        .receiptHandle(message.receiptHandle())
                        .build();
                sqsClient.deleteMessage(deleteMessageRequest);
            }
```

GitHub의 [전체 예제](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L187)를 참조하십시오.

## 추가 정보
<a name="more-info"></a>
+  개발자 안내서의 Amazon Simple Queue Service [Amazon Simple Queue Service 대기열 작동 방식](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-architecture.html) 
+  Amazon Simple Queue Service API 참조의 [SendMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html) 
+  Amazon Simple Queue Service API 참조의 [SendMessageBatch](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessageBatch.html) 
+  Amazon Simple Queue Service API 참조의 [ReceiveMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html) 
+  Amazon Simple Queue Service API 참조의 [DeleteMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteMessage.html) 

# 작업 Amazon Transcribe
<a name="examples-transcribe"></a>

다음 예제는 Amazon Transcribe를 사용하여 양방향 스트리밍이 작동하는 방식을 보여 줍니다. 양방향 스트리밍은 서비스로 향하는 데이터 스트림과 실시간으로 다시 수신되는 데이터 스트림이 둘 다 있다는 의미입니다. 이 예제에서는 Amazon Transcribe 스트리밍 트랜스크립션을 사용하여 오디오 스트림을 보내고 트랜스크립션된 텍스트 스트림을 실시간으로 다시 수신합니다.

이 기능에 대한 자세한 내용은 Amazon Transcribe 개발자 안내서의 [스트리밍 트랜스크립션](https://docs.aws.amazon.com//transcribe/latest/dg/streaming.html)을 참조하세요.

사용을 [시작하려면](https://docs.aws.amazon.com//transcribe/latest/dg/getting-started.html) Amazon Transcribe 개발자 안내서의 시작하기를 참조하세요 Amazon Transcribe.

## 마이크 설정
<a name="set-up-the-microphone"></a>

이 코드는 javax.sound.sampled 패키지를 사용하여 입력 디바이스에서 오디오를 스트리밍합니다.

 **코드** 

```
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;

public class Microphone {

    public static TargetDataLine get() throws Exception {
        AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
        DataLine.Info datalineInfo = new DataLine.Info(TargetDataLine.class, format);

        TargetDataLine dataLine = (TargetDataLine) AudioSystem.getLine(datalineInfo);
        dataLine.open(format);

        return dataLine;
    }
}
```

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

## 게시자 생성
<a name="create-a-publisher"></a>

이 코드는 오디오 스트림의 Amazon Transcribe 오디오 데이터를 게시하는 게시자를 구현합니다.

 **코드** 

```
package com.amazonaws.transcribe;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.transcribestreaming.model.AudioEvent;
import software.amazon.awssdk.services.transcribestreaming.model.AudioStream;
import software.amazon.awssdk.services.transcribestreaming.model.TranscribeStreamingException;


public class AudioStreamPublisher implements Publisher<AudioStream> {
    private final InputStream inputStream;

    public AudioStreamPublisher(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public void subscribe(Subscriber<? super AudioStream> s) {
        s.onSubscribe(new SubscriptionImpl(s, inputStream));
    }

    private class SubscriptionImpl implements Subscription {
        private static final int CHUNK_SIZE_IN_BYTES = 1024 * 1;
        private ExecutorService executor = Executors.newFixedThreadPool(1);
        private AtomicLong demand = new AtomicLong(0);

        private final Subscriber<? super AudioStream> subscriber;
        private final InputStream inputStream;

        private SubscriptionImpl(Subscriber<? super AudioStream> s, InputStream inputStream) {
            this.subscriber = s;
            this.inputStream = inputStream;
        }

        @Override
        public void request(long n) {
            if (n <= 0) {
                subscriber.onError(new IllegalArgumentException("Demand must be positive"));
            }

            demand.getAndAdd(n);

            executor.submit(() -> {
                try {
                    do {
                        ByteBuffer audioBuffer = getNextEvent();
                        if (audioBuffer.remaining() > 0) {
                            AudioEvent audioEvent = audioEventFromBuffer(audioBuffer);
                            subscriber.onNext(audioEvent);
                        } else {
                            subscriber.onComplete();
                            break;
                        }
                    } while (demand.decrementAndGet() > 0);
                } catch (TranscribeStreamingException e) {
                    subscriber.onError(e);
                }
            });
        }

        @Override
        public void cancel() {

        }

        private ByteBuffer getNextEvent() {
            ByteBuffer audioBuffer;
            byte[] audioBytes = new byte[CHUNK_SIZE_IN_BYTES];

            int len = 0;
            try {
                len = inputStream.read(audioBytes);

                if (len <= 0) {
                    audioBuffer = ByteBuffer.allocate(0);
                } else {
                    audioBuffer = ByteBuffer.wrap(audioBytes, 0, len);
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }

            return audioBuffer;
        }

        private AudioEvent audioEventFromBuffer(ByteBuffer bb) {
            return AudioEvent.builder()
                    .audioChunk(SdkBytes.fromByteBuffer(bb))
                    .build();
        }
    }
}
```

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

## 클라이언트 생성 및 스트림 시작
<a name="create-the-client-and-start-the-stream"></a>

주 메서드에서 요청 객체를 만들고 오디오 입력 스트림을 시작하며 오디오 입력으로 게시자를 인스턴스화합니다.

또한 [StartStreamTranscriptionResponseHandler](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/transcribestreaming/model/StartStreamTranscriptionResponseHandler.html)를 생성하여 응답을 처리하는 방법을 지정해야 합니다 Amazon Transcribe.

그런 다음 TranscribeStreamingAsyncClient의 `startStreamTranscription` 메서드를 사용하여 양방향 스트리밍을 시작합니다.

 **가져오기** 

```
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.AudioInputStream;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.transcribestreaming.TranscribeStreamingAsyncClient;
import software.amazon.awssdk.services.transcribestreaming.model.TranscribeStreamingException ;
import software.amazon.awssdk.services.transcribestreaming.model.StartStreamTranscriptionRequest;
import software.amazon.awssdk.services.transcribestreaming.model.MediaEncoding;
import software.amazon.awssdk.services.transcribestreaming.model.LanguageCode;
import software.amazon.awssdk.services.transcribestreaming.model.StartStreamTranscriptionResponseHandler;
import software.amazon.awssdk.services.transcribestreaming.model.TranscriptEvent;
```

 **코드** 

```
    public static void convertAudio(TranscribeStreamingAsyncClient client) throws Exception {

        try {

            StartStreamTranscriptionRequest request = StartStreamTranscriptionRequest.builder()
                    .mediaEncoding(MediaEncoding.PCM)
                    .languageCode(LanguageCode.EN_US)
                    .mediaSampleRateHertz(16_000).build();

            TargetDataLine mic = Microphone.get();
            mic.start();

            AudioStreamPublisher publisher = new AudioStreamPublisher(new AudioInputStream(mic));

            StartStreamTranscriptionResponseHandler response =
                    StartStreamTranscriptionResponseHandler.builder().subscriber(e -> {
                        TranscriptEvent event = (TranscriptEvent) e;
                        event.transcript().results().forEach(r -> r.alternatives().forEach(a -> System.out.println(a.transcript())));
                    }).build();

            // Keeps Streaming until you end the Java program
            client.startStreamTranscription(request, publisher, response);

        } catch (TranscribeStreamingException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
         }
    }
```

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

## 추가 정보
<a name="more-info"></a>
+  Amazon Transcribe 개발자 안내서의 [작동 방식](https://docs.aws.amazon.com//transcribe/latest/dg/how-it-works.html).
+  Amazon Transcribe 개발자 안내서의 [오디오 스트리밍 시작하기](https://docs.aws.amazon.com//transcribe/latest/dg/getting-started.html)를 참조하세요.