

# 로컬 보조 인덱스로 작업: Java
<a name="LSIJavaDocumentAPI"></a>

AWS SDK for Java Document API를 사용하여 하나 이상의 로컬 보조 인덱스가 포함된 Amazon DynamoDB 테이블을 만들고, 테이블의 인덱스를 설명하고, 인덱스를 사용하여 쿼리를 수행할 수 있습니다.

다음은 AWS SDK for Java Document API를 사용하여 테이블 작업을 할 때 따라야 할 공통 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. 해당하는 요청 객체를 만들어 작업의 필수 및 선택적 파라미터를 제공합니다.

1. 이전 단계에서 만든 클라이언트가 제공한 적절한 메서드를 호출합니다.

**Topics**
+ [로컬 보조 인덱스가 있는 테이블 생성](#LSIJavaDocumentAPI.CreateTableWithIndex)
+ [로컬 보조 인덱스가 있는 테이블 설명](#LSIJavaDocumentAPI.DescribeTableWithIndex)
+ [로컬 보조 인덱스 쿼리](#LSIJavaDocumentAPI.QueryAnIndex)
+ [예: Java 문서 API를 사용하는 로컬 보조 인덱스](LSIJavaDocumentAPI.Example.md)

## 로컬 보조 인덱스가 있는 테이블 생성
<a name="LSIJavaDocumentAPI.CreateTableWithIndex"></a>

로컬 보조 인덱스는 테이블을 만들 때 동시에 만들어야 합니다. 이렇게 하려면 `createTable` 메서드를 사용하여 하나 이상의 로컬 보조 인덱스 사양을 입력합니다. 다음 Java 코드 예제는 보유한 음악 파일에 있는 곡의 정보를 담은 테이블을 만듭니다. 파티션 키는 `Artist`이고 정렬 키는 `SongTitle`입니다. 보조 인덱스인 `AlbumTitleIndex`는 앨범 제목을 사용해 쿼리를 쉽게 수행하는 데 사용합니다.

다음은 DynamoDB Document API를 사용하여 로컬 보조 인덱스가 있는 테이블을 생성하는 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. `CreateTableRequest` 클래스 인스턴스를 만들어 요청 정보를 입력합니다.

   이때 입력해야 하는 정보는 테이블 이름, 기본 키, 그리고 프로비저닝된 처리량 값입니다. 로컬 보조 인덱스의 경우 인덱스 이름, 인덱스 정렬 키의 이름 및 데이터 형식, 인덱스의 키 스키마, 속성 프로젝션을 입력해야 합니다.

1. 요청 객체를 파라미터로 입력하여 `createTable` 메서드를 호출합니다.

다음 Java 코드 예는 앞의 단계를 보여줍니다. 이 코드는 `Music` 속성에 보조 인덱스가 있는 테이블(`AlbumTitle`)을 생성합니다. 인덱스에 프로젝션되는 속성은 테이블 파티션 키 및 정렬 키와 인덱스 정렬 키뿐입니다.

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName);

//ProvisionedThroughput
createTableRequest.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits((long)5).withWriteCapacityUnits((long)5));

//AttributeDefinitions
ArrayList<AttributeDefinition> attributeDefinitions= new ArrayList<AttributeDefinition>();
attributeDefinitions.add(new AttributeDefinition().withAttributeName("Artist").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("SongTitle").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("AlbumTitle").withAttributeType("S"));

createTableRequest.setAttributeDefinitions(attributeDefinitions);

//KeySchema
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
tableKeySchema.add(new KeySchemaElement().withAttributeName("SongTitle").withKeyType(KeyType.RANGE));  //Sort key

createTableRequest.setKeySchema(tableKeySchema);

ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>();
indexKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
indexKeySchema.add(new KeySchemaElement().withAttributeName("AlbumTitle").withKeyType(KeyType.RANGE));  //Sort key

Projection projection = new Projection().withProjectionType(ProjectionType.INCLUDE);
ArrayList<String> nonKeyAttributes = new ArrayList<String>();
nonKeyAttributes.add("Genre");
nonKeyAttributes.add("Year");
projection.setNonKeyAttributes(nonKeyAttributes);

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex()
    .withIndexName("AlbumTitleIndex").withKeySchema(indexKeySchema).withProjection(projection);

ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new ArrayList<LocalSecondaryIndex>();
localSecondaryIndexes.add(localSecondaryIndex);
createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes);

Table table = dynamoDB.createTable(createTableRequest);
System.out.println(table.getDescription());
```

DynamoDB에서 테이블을 만들고 테이블 상태가 `ACTIVE`로 설정될 때까지 기다려야 합니다. 그런 다음 테이블에 데이터 항목을 입력할 수 있습니다.

## 로컬 보조 인덱스가 있는 테이블 설명
<a name="LSIJavaDocumentAPI.DescribeTableWithIndex"></a>

테이블의 로컬 보조 인덱스에 관한 자세한 내용은 `describeTable` 메서드를 참조하세요. 각 인덱스에 대해 인덱스의 이름, 키 스키마 및 프로젝션된 속성에 액세스할 수 있습니다.

다음은 AWS SDK for Java Document API를 사용하여 테이블의 로컬 보조 인덱스 정보에 액세스하는 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. `Table` 클래스의 인스턴스를 만듭니다. 테이블 이름을 입력해야 합니다.

1. `describeTable` 객체의 `Table` 메서드를 호출합니다.

다음 Java 코드 예는 앞의 단계를 보여줍니다.

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);

TableDescription tableDescription = table.describe();

List<LocalSecondaryIndexDescription> localSecondaryIndexes 
    = tableDescription.getLocalSecondaryIndexes();

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.

Iterator<LocalSecondaryIndexDescription> lsiIter = localSecondaryIndexes.iterator();
while (lsiIter.hasNext()) {

    LocalSecondaryIndexDescription lsiDescription = lsiIter.next();
    System.out.println("Info for index " + lsiDescription.getIndexName() + ":");
    Iterator<KeySchemaElement> kseIter = lsiDescription.getKeySchema().iterator();
    while (kseIter.hasNext()) {
        KeySchemaElement kse = kseIter.next();
        System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType());
    }
    Projection projection = lsiDescription.getProjection();
    System.out.println("\tThe projection type is: " + projection.getProjectionType());
    if (projection.getProjectionType().toString().equals("INCLUDE")) {
        System.out.println("\t\tThe non-key projected attributes are: " + projection.getNonKeyAttributes());
    }
}
```

## 로컬 보조 인덱스 쿼리
<a name="LSIJavaDocumentAPI.QueryAnIndex"></a>

테이블을 `Query`할 때와 거의 동일한 방식으로 로컬 보조 인덱스에서 `Query` 작업을 사용할 수 있습니다. 인덱스 이름, 인덱스 정렬 키의 쿼리 기준, 반환하려는 속성을 지정해야 합니다. 이 예제에서 인덱스는 `AlbumTitleIndex`이고 인덱스 정렬 키는 `AlbumTitle`입니다.

인덱스로 프로젝션된 속성만 반환됩니다. 키가 아닌 속성을 선택하도록 이 쿼리를 수정할 수도 있지만, 그렇게 하려면 비교적 많은 비용이 드는 테이블 가져오기 작업이 필요합니다. 테이블 가져오기에 대한 자세한 내용은 [속성 프로젝션](LSI.md#LSI.Projections) 단원을 참조하세요.

다음은 AWS SDK for Java Document API를 사용하여 로컬 보조 인덱스를 쿼리하는 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. `Table` 클래스의 인스턴스를 만듭니다. 테이블 이름을 입력해야 합니다.

1. `Index` 클래스의 인스턴스를 만듭니다. 인덱스 이름을 입력해야 합니다.

1. `query` 클래스의 `Index` 메서드를 호출합니다.

다음 Java 코드 예는 앞의 단계를 보여줍니다.

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);
Index index = table.getIndex("AlbumTitleIndex");

QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"));

ItemCollection<QueryOutcome> items = index.query(spec);

Iterator<Item> itemsIter = items.iterator();

while (itemsIter.hasNext()) {
    Item item = itemsIter.next();
    System.out.println(item.toJSONPretty());
}
```

### 로컬 보조 인덱스에서 일관된 읽기
<a name="LSIJavaDocumentAPI.ConsistentReads"></a>

최종적으로 일관된 읽기만 지원하는 글로벌 보조 인덱스와 달리, 로컬 보조 인덱스는 최종적으로 일관된 읽기와 강력하게 일관된 읽기를 모두 지원합니다. 로컬 보조 인덱스에서 강력히 일관된 읽기는 항상 업데이트된 최신 값을 반환합니다. 쿼리가 기본 테이블에서 추가 속성을 가져와야 하는 경우 가져온 해당 속성도 마찬가지로 인덱스와 일관성을 유지합니다.

기본적으로 `Query`는 최종적으로 일관된 읽기를 사용합니다. 강력하게 일관된 읽기를 요청하려면 `QuerySpec`에서 `ConsistentRead`를 `true`로 설정합니다. 다음 예제에서는 강력하게 일관된 읽기를 사용하여 `AlbumTitleIndex`를 쿼리합니다.

**Example**  

```
QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"))
    .withConsistentRead(true);
```

**참고**  
강력하게 일관된 읽기는 반환된(반올림된) 데이터 4KB당 하나의 읽기 용량 단위를 소비하는 반면, 최종적으로 일관된 읽기는 그중 절반을 소비합니다. 예를 들어 9KB의 데이터를 반환하는 강력하게 일관된 읽기는 3개의 읽기 용량 단위(9KB/4KB = 2.25, 반올림 3)를 사용하는 반면, 최종적으로 일관된 읽기를 사용하는 동일한 쿼리는 1.5개의 읽기 용량 단위를 사용합니다. 애플리케이션이 약간 오래된 데이터 읽기를 허용할 수 있는 경우 최종적으로 일관된 읽기를 사용하여 읽기 용량 사용량을 줄입니다. 자세한 내용은 [읽기 용량 단위](LSI.md#LSI.ThroughputConsiderations.Reads) 섹션을 참조하세요.

# 예: Java 문서 API를 사용하는 로컬 보조 인덱스
<a name="LSIJavaDocumentAPI.Example"></a>

다음 Java 코드 예제는 Amazon DynamoDB에서 로컬 보조 인덱스로 작업하는 방법을 보여 줍니다. 예를 들어 파티션 키가 `CustomerOrders`이고 정렬 키가 `CustomerId`인 `OrderId`라는 테이블을 만들 수 있습니다. 이 테이블에는 두 개의 로컬 보조 인덱스가 있습니다.
+ `OrderCreationDateIndex` - 정렬 키는 `OrderCreationDate`이며 다음 속성이 인덱스로 프로젝션됩니다.
  + `ProductCategory`
  + `ProductName`
  + `OrderStatus`
  + `ShipmentTrackingId`
+ `IsOpenIndex` - 정렬 키는 `IsOpen`이며 모든 테이블 속성이 인덱스로 프로젝션됩니다.

`CustomerOrders` 테이블이 생성되면 프로그램에서 고객 주문을 나타내는 데이터와 함께 테이블을 로드합니다. 그런 다음 로컬 보조 인덱스를 사용하여 데이터를 쿼리합니다. 마지막으로 프로그램에서 `CustomerOrders` 테이블을 삭제합니다.

다음 샘플을 테스트하기 위한 단계별 지침은 [Java 코드 예](CodeSamples.Java.md) 섹션을 참조하세요.

**Example**  

```
package com.example.dynamodb;

import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;

import java.util.HashMap;
import java.util.Map;

public class DocumentAPILocalSecondaryIndexExample {

    static DynamoDbClient client = DynamoDbClient.create();
    public static String tableName = "CustomerOrders";

    public static void main(String[] args) {
        createTable();
        loadData();
        query(null);
        query("IsOpenIndex");
        query("OrderCreationDateIndex");
        deleteTable(tableName);
    }

    public static void createTable() {
        CreateTableRequest request = CreateTableRequest.builder()
            .tableName(tableName)
            .provisionedThroughput(ProvisionedThroughput.builder()
                .readCapacityUnits(1L)
                .writeCapacityUnits(1L)
                .build())
            .attributeDefinitions(
                AttributeDefinition.builder().attributeName("CustomerId").attributeType(ScalarAttributeType.S).build(),
                AttributeDefinition.builder().attributeName("OrderId").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("OrderCreationDate").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("IsOpen").attributeType(ScalarAttributeType.N).build())
            .keySchema(
                KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                KeySchemaElement.builder().attributeName("OrderId").keyType(KeyType.RANGE).build())
            .localSecondaryIndexes(
                LocalSecondaryIndex.builder()
                    .indexName("OrderCreationDateIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("OrderCreationDate").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.INCLUDE)
                        .nonKeyAttributes("ProductCategory", "ProductName")
                        .build())
                    .build(),
                LocalSecondaryIndex.builder()
                    .indexName("IsOpenIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("IsOpen").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.ALL)
                        .build())
                    .build())
            .build();

        System.out.println("Creating table " + tableName + "...");
        client.createTable(request);

        try (DynamoDbWaiter waiter = client.waiter()) {
            WaiterResponse<DescribeTableResponse> response = waiter.waitUntilTableExists(r -> r.tableName(tableName));
            response.matched().response().ifPresent(System.out::println);
        }
    }

    public static void query(String indexName) {
        System.out.println("\n***********************************************************\n");
        System.out.println("Querying table " + tableName + "...");

        if ("IsOpenIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that are open.");
            System.out.println("Only a user-specified list of attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_isopen", AttributeValue.builder().n("1").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and IsOpen = :v_isopen")
                .expressionAttributeValues(values)
                .projectionExpression("OrderCreationDate, ProductCategory, ProductName, OrderStatus")
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else if ("OrderCreationDateIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that were placed after 01/31/2015.");
            System.out.println("Only the projected attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_orddate", AttributeValue.builder().n("20150131").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and OrderCreationDate >= :v_orddate")
                .expressionAttributeValues(values)
                .select(Select.ALL_PROJECTED_ATTRIBUTES)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else {
            System.out.println("\nNo index: All of Bob's orders, by OrderId:\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .keyConditionExpression("CustomerId = :v_custid")
                .expressionAttributeValues(values)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);
        }
    }

    public static void deleteTable(String tableName) {
        System.out.println("Deleting table " + tableName + "...");
        client.deleteTable(DeleteTableRequest.builder().tableName(tableName).build());

        try (DynamoDbWaiter waiter = client.waiter()) {
            waiter.waitUntilTableNotExists(r -> r.tableName(tableName));
        }
    }

    public static void loadData() {
        System.out.println("Loading data into table " + tableName + "...");

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150101").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("The Great Outdoors").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Bike").build(),
            "ProductName", AttributeValue.builder().s("Super Mountain").build(),
            "OrderStatus", AttributeValue.builder().s("ORDER RECEIVED").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150304").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("A Quiet Interlude").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("176493").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150111").build(),
            "ProductCategory", AttributeValue.builder().s("Movie").build(),
            "ProductName", AttributeValue.builder().s("Calm Before The Storm").build(),
            "OrderStatus", AttributeValue.builder().s("SHIPPING DELAY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("859323").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150124").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("E-Z Listening").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("756943").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("Symphony 9").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("645193").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("4").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150222").build(),
            "ProductCategory", AttributeValue.builder().s("Hardware").build(),
            "ProductName", AttributeValue.builder().s("Extra Heavy Hammer").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("5").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150309").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("How To Cook").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("440185").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("6").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150318").build(),
            "ProductCategory", AttributeValue.builder().s("Luggage").build(),
            "ProductName", AttributeValue.builder().s("Really Big Suitcase").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("893927").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("7").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150324").build(),
            "ProductCategory", AttributeValue.builder().s("Golf").build(),
            "ProductName", AttributeValue.builder().s("PGA Pro II").build(),
            "OrderStatus", AttributeValue.builder().s("OUT FOR DELIVERY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("383283").build()));
    }

    private static void putItem(Map<String, AttributeValue> item) {
        client.putItem(PutItemRequest.builder().tableName(tableName).item(item).build());
    }
}
```