

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

# Saiba mais sobre os fundamentos da API do cliente avançado do DynamoDB
<a name="ddb-en-client-use"></a>

Este tópico discute os atributos básicos da API do Cliente Aprimorado do DynamoDB e a compara com a [API do cliente padrão do DynamoDB](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html).

Se você não conhece a API do Cliente Aprimorado do DynamoDB, recomendamos que leia o [tutorial introdutório](ddb-en-client-getting-started.md) para se familiarizar com as classes fundamentais.

## Itens do DynamoDB em Java
<a name="ddb-en-client-use-usecase"></a>

As tabelas do DynamoDB armazenam itens. Dependendo do seu caso de uso, os itens no lado Java podem assumir a forma de dados estruturados por estatísticas ou estruturas criadas de modo dinâmico. 

Se seu caso de uso exigir itens com um conjunto consistente de atributos, use [classes anotadas](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean) ou use um [construtor](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder) para gerar a tipagem estática apropriada `TableSchema`. 

Como alternativa, se você precisar armazenar itens que consistem em estruturas variadas, crie um `DocumentTableSchema`. `DocumentTableSchema` faz parte da [API de Documento Aprimorado](ddb-en-client-doc-api.md) e requer somente uma chave primária digitada estaticamente e funciona com instâncias `EnhancedDocument` para armazenar os elementos de dados. A API de Documento Aprimorado é abordada em outro [tópico.](ddb-en-client-doc-api.md)

## Tipos de atributos para classes de modelo de dados
<a name="ddb-en-client-use-types"></a>

Embora o DynamoDB ofereça suporte a [um pequeno número de tipos de atributos](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes) em comparação com o sistema de tipos avançados do Java, a API do Cliente Aprimorado do DynamoDB fornece mecanismos para converter membros de uma classe Java de e para tipos de atributos do DynamoDB.

Os tipos de atributos (propriedades) de suas classes de dados do Java devem ser tipos de objetos, não primitivos. Por exemplo, sempre use tipos de dados de objetos `Long` e `Integer`, não primitivos `long` e `int`.

[Por padrão, a API do DynamoDB Enhanced Client suporta conversores de atributos para um grande número de tipos, [como Integer[,](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html) String](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html) e Instant. [BigDecimal](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/BigDecimalAttributeConverter.html)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/InstantAsStringAttributeConverter.html) A lista aparece nas [classes de implementação conhecidas da AttributeConverter interface](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html). A lista inclui muitos tipos e coleções, como mapas, listas e conjuntos.

Para armazenar os dados de um tipo de atributo que não é suportado por padrão ou não está em conformidade com a JavaBean convenção, você pode escrever uma `AttributeConverter` implementação personalizada para fazer a conversão. Consulte a seção de conversão de atributos para ver um [exemplo](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-example).

Para armazenar os dados de um tipo de atributo cuja classe está em conformidade com a especificação Java Beans (ou uma [classe de dados imutável](ddb-en-client-use-immut.md)), você pode adotar duas abordagens. 
+ Se você tiver acesso ao arquivo de origem, poderá anotar a classe com `@DynamoDbBean` (ou `@DynamoDbImmutable`). A seção que discute atributos aninhados mostra [exemplos](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-anno) do uso de classes anotadas.
+ Se você não tiver acesso ao arquivo de origem da classe de JavaBean dados do atributo (ou não quiser anotar o arquivo de origem de uma classe à qual você tem acesso), você pode usar a abordagem do construtor. Isso cria um esquema de tabela sem definir as chaves. Em seguida, você pode aninhar esse esquema de tabela dentro de outro esquema de tabela para realizar o mapeamento. A seção de atributos aninhados tem um [exemplo](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-builder) que mostra o uso de esquemas aninhados.

### Valores nulos
<a name="ddb-en-client-use-types-nulls"></a>

Quando você usa o método `putItem`, o cliente aprimorado não inclui atributos de valor nulo de um objeto de dados mapeado na solicitação ao DynamoDB.

O comportamento padrão do SDK para solicitações `updateItem` remove atributos do item no DynamoDB que estão definidos como nulos no objeto que você envia no método `updateItem`. Se você pretende atualizar alguns valores de atributos e manter os outros inalterados, existem duas opções:
+ Recuperar o item (usando `getItem`) antes de fazer alterações nos valores. Ao usar essa abordagem, o SDK envia todos os valores antigos e atualizados para o DynamoDB.
+ Usar o `[IgnoreNullsMode](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html).SCALAR_ONLY` ou `IgnoreNullsMode.MAPS_ONLY` ao criar a solicitação para atualizar o item. Ambos os modos ignoram propriedades de valor nulo no objeto que representam atributos escalares no DynamoDB. O tópico [Atualizar itens que contêm tipos complexos](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-updates) deste guia contém mais informações sobre os valores de `IgnoreNullsMode` e como trabalhar com tipos complexos.

O exemplo a seguir demonstra `ignoreNullsMode()` para o método `updateItem()`.

```
    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]
```

## Métodos básicos do Cliente Aprimorado do DynamoDB
<a name="ddb-en-client-use-basic-ops"></a>

Os métodos básicos do cliente aprimorado mapeiam as operações de serviço do DynamoDB que deram nome a eles. Os exemplos a seguir mostram a variação mais simples de cada método. Você pode personalizar cada método passando um objeto de solicitação aprimorado. Os objetos de solicitação aprimorada oferecem a maioria dos atributos disponíveis no cliente padrão do DynamoDB. Eles estão totalmente documentados na Referência da API do AWS SDK for Java 2.x .

O exemplo usa o [Classe `Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) mostrado anteriormente.

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

## Comparar o Cliente Aprimorado do DynamoDB com o cliente padrão do DynamoDB
<a name="ddb-en-client-use-compare"></a>

Tanto o APIs cliente DynamoDB [— padrão [quanto](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html) aprimorado —](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html) permitem que você trabalhe com tabelas do DynamoDB para realizar operações CRUD (criar, ler, atualizar e excluir) em nível de dados. A diferença entre o cliente APIs é como isso é feito. Usando o cliente padrão, você trabalha diretamente com atributos de dados de nível baixo. A API do cliente aprimorado usa classes Java conhecidas e mapeia a API de nível baixo nos bastidores.

Embora ambos os clientes ofereçam APIs suporte a operações em nível de dados, o cliente padrão do DynamoDB também oferece suporte a operações em nível de recursos. As operações em nível de recurso gerenciam o banco de dados, como criar backups, listar e atualizar tabelas. A API do cliente aprimorado fornece suporte a um número selecionado de operações em nível de recurso, como criar, descrever e excluir tabelas.

Para ilustrar as diferentes abordagens usadas pelos dois clientes APIs, os exemplos de código a seguir mostram a criação da mesma `ProductCatalog` tabela usando o cliente padrão e o cliente aprimorado.

### Comparar: criar uma tabela usando o cliente do DynamoDB padrão
<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))
);
```

### Comparar: criar uma tabela usando o Cliente Aprimorado do 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))
        )
);
```

O cliente aprimorado usa a seguinte classe de dados anotada: O Cliente Aprimorado do DynamoDB mapeia tipos de dados Java para tipos de dados do DynamoDB para obter um código menos detalhado e mais fácil de seguir. `ProductCatalog` é um exemplo do uso de uma classe imutável com o Cliente Aprimorado do DynamoDB. O uso de classes imutáveis para classes de dados mapeados será [discutido posteriormente](ddb-en-client-use-immut.md) neste tópico.

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

Os dois exemplos de código de uma gravação em lote a seguir ilustram a verbosidade e a falta de segurança de tipo ao usar o cliente padrão em vez do cliente aprimorado.

### Comparar: operação de gravação em lote usando o cliente DynamoDB padrão
<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());
    }
```

### Comparar: operação de gravação em lote usando o Cliente Aprimorado do 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());
    }
```

# Trabalhar com classes de dados imutáveis
<a name="ddb-en-client-use-immut"></a>

O atributo de mapeamento da API do Cliente Aprimorado do DynamoDB funciona com classes de dados imutáveis. Uma classe imutável tem apenas getters e requer uma classe construtora que o SDK usa para criar instâncias da classe. Em vez de usar a anotação `@DynamoDbBean` conforme mostrado na [classe Customer](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust), as classes imutáveis usam a anotação `@DynamoDbImmutable`, a qual usa um parâmetro que indica a classe do construtor a ser usada.

A classe a seguir é uma versão imutável de `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); }
    }
}
```

Você deve atender aos seguintes requisitos ao anotar uma classe de dados com `@DynamoDbImmutable`.

1. Todo método que não é uma substituição de `Object.class` e não foi anotado com `@DynamoDbIgnore` deve ser um getter de um atributo da tabela do DynamoDB.

1. Cada getter deve ter um setter correspondente com distinção entre maiúsculas e minúsculas na classe builder.

1. Somente uma das seguintes condições de estrutura precisa ser atendida:
   + A classe builder deve ter um construtor padrão público.
   + A classe de dados deve ter um método estático público chamado `builder()` que não usa parâmetros e que retorna uma instância da classe builder. Essa opção é mostrada na classe imutável `Customer`.

1.  A classe builder deve ter um método público chamado `build()` que não use parâmetros e retorne uma instância da classe imutável. 

Para criar um `TableSchema` para sua classe imutável, use o método `fromImmutableClass()` no `TableSchema` conforme mostrado no trecho a seguir.

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

Assim como você pode criar uma tabela do DynamoDB a partir de uma classe mutável, você pode criar uma a partir de uma classe imutável com uma chamada *única* para `createTable()` do `DynamoDbTable` conforme mostrado no exemplo de trecho a seguir.

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

## Usar bibliotecas de terceiros, como Lombok
<a name="ddb-en-client-use-immut-lombok"></a>

Bibliotecas de terceiros, como [Project Lombok](https://projectlombok.org/), ajudam a gerar código clichê associado a objetos imutáveis. A API do Cliente Aprimorado do DynamoDB funciona com essas bibliotecas, desde que as classes de dados sigam as convenções detalhadas nesta seção. 

O exemplo a seguir mostra a classe `CustomerImmutable` imutável com anotações do Lombok. Observe como o atributo `onMethod` do Lombok copia anotações do DynamoDB baseadas em atributos, como `@DynamoDbPartitionKey`, no código gerado.

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

# Usar expressões e condições
<a name="ddb-en-client-expressions"></a>

As expressões na API do Cliente Aprimorado do DynamoDB são representações Java das [expressões do DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html).

A API do Cliente Aprimorado do DynamoDB usa três tipos de expressões:

[Expressão](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)  
A classe `Expression` é usada quando você define condições e filtros.

[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)  
Esse tipo de expressão representa as [principais condições](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions) para operações de consulta.

[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)  
Essa classe ajuda você a escrever [expressões de atualização](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html) do DynamoDB e é usada atualmente na estrutura de extensão quando você atualiza um item.

## Anatomia da expressão
<a name="ddb-en-client-expressions-compoonents"></a>

Uma expressão é composta do seguinte:
+ Uma expressão em cadeia de caracteres (obrigatório). Uma cadeia de caracteres contém uma expressão lógica do DynamoDB com nomes de espaço reservado para nomes e valores de atributos.
+ Um mapa dos valores da expressão (geralmente obrigatório).
+ Um mapa dos nomes das expressões (opcional).

Use um construtor para gerar um objeto `Expression` que tenha a seguinte forma geral.

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

`Expression`s geralmente exigem um mapa dos valores da expressão. O mapa fornece os valores dos espaços reservados na expressão da cadeia de caracteres. A chave do mapa consiste no nome do espaço reservado precedido por dois pontos (`:`) e o valor do mapa é uma instância de. [AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) A [AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/AttributeValues.html)classe tem métodos convenientes para gerar uma `AttributeValue` instância a partir de um literal. Como alternativa, você pode usar o `AttributeValue.Builder` para gerar uma instância `AttributeValue`.

O trecho a seguir mostra um mapa com duas entradas após a linha de comentário 2. A cadeia de caracteres passada para o método `expression()`, mostrada após a linha de comentário 1, contém os espaços reservados que o DynamoDB determina antes de realizar a operação. Esse trecho não contém um mapa dos nomes das expressões, porque *price* é um nome de atributo permissível.

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

Se um nome de atributo na tabela do DynamoDB for uma palavra reservada, começar com um número ou contiver um espaço, um mapa dos nomes das expressões será necessário para a `Expression`.

Por exemplo, se o nome do atributo fosse `1price` em vez de `price` no exemplo de código anterior, o exemplo precisaria ser modificado conforme mostrado no exemplo a seguir.

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

Um espaço reservado para o nome de uma expressão começa com o sinal de libra (`#`). Uma entrada para o mapa de nomes de expressão usa o espaço reservado como chave e o nome do atributo como valor. O mapa é adicionado ao construtor de expressões com o método `expressionNames()`. O DynamoDB resolve o nome do atributo antes de realizar a operação.

Os valores de expressão não são necessários se uma função for usada na expressão da cadeia de caracteres. Um exemplo de função de expressão é `attribute_exists(<attribute_name>)`.

O exemplo a seguir cria uma `Expression` que usa uma [função do DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions). Uma cadeia de caracteres de expressão neste exemplo não usa espaços reservados. Essa expressão pode ser usada em uma operação `putItem` para verificar se um item já existe no banco de dados com um valor de atributo `movie` igual ao atributo `movie` do objeto de dados.

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

O Guia do desenvolvedor do DynamoDB contém informações completas sobre as expressões[ de nível baixo](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html) usadas com o DynamoDB.

## Expressões de condição e condicionais
<a name="ddb-en-client-expressions-cond"></a>

Ao usar os métodos `putItem()`, `updateItem()`, `deleteItem()` e também ao usar operações de transação e em lote, você usa objetos de `[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)` para especificar as condições que o DynamoDB deve atender para continuar com a operação. Essas expressões são chamadas de expressões de condição. Para ver um exemplo, consulte a expressão de condição usada no método `addDeleteItem()` (após a linha de comentário 1) do [exemplo de transação](ddb-en-client-use-multiop-trans.md#ddb-en-client-use-multiop-trans-writeitems-opcondition) mostrado neste guia.

Quando você trabalha com os métodos `query()`, uma condição é expressa como uma [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). A classe `QueryConditional` tem vários métodos estáticos de conveniência que ajudam você a escrever os critérios que determinam quais itens ler no DynamoDB.

Para ver exemplos de `QueryConditionals`, consulte o primeiro exemplo de código da seção [Exemplos de métodos `Query`](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-query-example) deste guia.

## Expressões de filtro
<a name="ddb-en-client-expressions-filter"></a>

As expressões de filtro são usadas em operações de digitalização e consulta para filtrar os itens retornados. 

Uma expressão de filtro é aplicada depois que todos os dados são lidos do banco de dados, de modo que o custo de leitura é o mesmo que se não houvesse filtro. O *Guia do desenvolvedor do Amazon DynamoDB* tem mais informações sobre o uso de expressões de filtro para operações de [consulta](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression) e [verificação](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression).

O exemplo a seguir mostra uma expressão de filtro adicionada a uma solicitação de verificação. O critério restringe os itens devolvidos a itens com um preço de 8,00 a 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();
```

## Expressões de atualização
<a name="ddb-en-client-expressions-update"></a>

O método `updateItem()` do Cliente Aprimorado do DynamoDB fornece uma forma padrão de atualizar itens no DynamoDB. [No entanto, quando você precisar de mais funcionalidades, [UpdateExpressions](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)forneça uma representação segura da sintaxe da expressão de atualização do DynamoDB.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html) Por exemplo, você pode usar `UpdateExpressions` para aumentar valores sem primeiro ler itens do DynamoDB ou adicionar membros individuais a uma lista. Atualmente, as expressões de atualização estão disponíveis em extensões personalizadas para o método `updateItem()`.

Para ver um exemplo que usa expressões de atualização, consulte o [exemplo de extensão personalizada](ddb-en-client-extensions-custom.md) neste guia.

Mais informações sobre expressões de atualização estão disponíveis no [Guia do desenvolvedor do Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html).

# Trabalhar com resultados paginados: verificações e consultas
<a name="ddb-en-client-use-multirecord"></a>

Os métodos `scan`, `query` e `batch` da API do Cliente Aprimorado do DynamoDB retornam respostas com uma ou mais *páginas*. Uma página contém um ou mais itens. Seu código pode processar a resposta por página ou pode processar itens individuais.

Uma resposta paginada retornada pelo `DynamoDbEnhancedClient` cliente síncrono retorna um [PageIterable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PageIterable.html)objeto, enquanto uma resposta retornada pelo `DynamoDbEnhancedAsyncClient` assíncrono retorna um objeto. [PagePublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PagePublisher.html)

Esta seção analisa o processamento de resultados paginados e fornece exemplos que usam a verificação e a consulta. APIs

## Verificar uma tabela
<a name="ddb-en-client-use-multirecord-scan"></a>

O método do 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)) corresponde à [operação do DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) com o mesmo nome. A API do Cliente Aprimorado do DynamoDB oferece as mesmas opções, mas usa um modelo de objeto familiar e gerencia a paginação para você.

Primeiro, exploramos a `PageIterable` interface examinando o `scan` método da classe de mapeamento síncrono, [DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html).

### Usar a API síncrona
<a name="ddb-en-client-use-multirecord-scan-sync"></a>

O exemplo a seguir mostra o método `scan` que usa uma [expressão](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html) para filtrar os itens retornados. [ProductCatalog](ddb-en-client-use.md#ddb-en-client-use-compare-cs3)É o objeto do modelo que foi mostrado anteriormente.

A expressão de filtragem mostrada após a linha de comentário 2 limita os `ProductCatalog` itens que são retornados àqueles com um valor de preço entre 8,00 e 80,00, inclusive.

Este exemplo também exclui os `isbn` valores usando o `attributesToProject` método mostrado após a linha de comentário 1.

Depois da linha de comentário 3, o objeto `PageIterable`, `pagedResults`, é retornado pelo método `scan`. O método `stream` de `PageIterable` retorna um objeto [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), que você pode usar para processar as páginas. Neste exemplo, o número de páginas é contado e registrado.

Começando com a linha de comentários 4, o exemplo mostra duas variações de acesso aos itens `ProductCatalog`. A versão após a linha de comentário 4a percorre cada página e classifica e registra os itens em cada página. A versão após a linha de comentário 4b ignora a iteração da página e acessa os itens diretamente.

A interface `PageIterable` oferece várias maneiras de processar resultados por causa de suas duas interfaces principais: [https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) e [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` traz os métodos `forEach`, `iterator` e `spliterator`, e `SdkIterable` traz o método `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())
                );
    }
```

### Usar a API assíncrona
<a name="ddb-en-client-use-multirecord-scan-async"></a>

O método `scan` assíncrono retorna os resultados como um objeto `PagePublisher`. A interface `PagePublisher` tem dois métodos `subscribe` que você pode usar para processar páginas de resposta. Um método `subscribe` vem da interface principal `org.reactivestreams.Publisher`. Para processar páginas usando essa primeira opção, transmita uma instância `[Subscriber](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Subscriber.html)` para o método `subscribe`. O primeiro exemplo a seguir mostra o uso do método `subscribe`.

O segundo `subscribe` método vem da [SdkPublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html)interface. Esta versão de `subscribe` aceita um [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) em vez de um `Subscriber`. Essa variação do método `subscribe` é mostrada no segundo exemplo.

O exemplo a seguir mostra a versão assíncrona do método `scan` que usa a mesma expressão de filtro mostrada no exemplo anterior. 

Após a linha de comentário 3, `DynamoDbAsyncTable.scan` retorna um objeto `PagePublisher`. Na próxima linha, o código cria uma instância da interface `org.reactivestreams.Subscriber`, `ProductCatalogSubscriber`, que assina a `PagePublisher` após o comentário na linha 4.

O objeto `Subscriber` coleta os itens `ProductCatalog` de cada página no método `onNext` após a linha de comentário 8 no exemplo da classe `ProductCatalogSubscriber`. Os itens são armazenados na varável `List` privada e acessados no código de chamada com o método `ProductCatalogSubscriber.getSubscribedItems()`. A chamado é feita após a linha de comentários 5.

Depois que a lista é recuperada, o código classifica todos os itens `ProductCatalog` por preço e registra cada item.

O [CountDownLatch](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html)in the `ProductCatalogSubscriber` class bloqueia o tópico de chamada até que todos os itens tenham sido adicionados à lista antes de continuar após a linha de comentários 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;
        }
    }
```

O exemplo de trecho a seguir usa a versão do método `PagePublisher.subscribe` que aceita um `Consumer` após a linha de comentário 6. O parâmetro lambda em Java consome páginas, que processam ainda mais cada item. Neste exemplo, cada página é processada e os itens em cada página são classificados e, em seguida, registrados.

```
        // 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.
```

O método `items` do `PagePublisher` desempacota as instâncias do modelo para que seu código possa processar os itens diretamente. Essa abordagem é mostrada no trecho a seguir.

```
        // 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.
```

## Consultar uma tabela
<a name="ddb-en-client-use-multirecord-query"></a>

É possível usar o Cliente Aprimorado do DynamoDB para consultar sua tabela e recuperar vários itens que correspondam a critérios específicos. O método [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)) encontra itens com base nos valores da chave primária usando as anotações `@DynamoDbPartitionKey` e, opcionalmente, `@DynamoDbSortKey` definidas em sua classe de dados.

O método `query()` requer um valor de chave de partição e, opcionalmente, aceita condições de chave de classificação para refinar ainda mais os resultados. Assim como a API `scan`, as consultas retornam um `PageIterable` para chamadas síncronas e um `PagePublisher` para chamadas assíncronas.

### Exemplos de métodos `Query`
<a name="ddb-en-client-use-multirecord-query-example"></a>

O exemplo de código do método `query()` a seguir usa a classe `MovieActor`. A classe de dados define uma chave primária composta constituída pelo atributo **`movie`** como a chave de partição e pelo atributo **`actor`** como a chave de classificação. 

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

Os exemplos de código a seguir são consultados com base nos itens a seguir.

#### Itens na tabela `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'}
```

O código a seguir define duas instâncias `QueryConditional`: `keyEqual` (após a linha de comentário 1) e `sortGreaterThanOrEqualTo` (após a linha de comentário 1a).

#### Consultar itens por chave de partição
<a name="keyEqual-query-conditional-example"></a>

A instância `keyEqual` combina itens com um valor de chave de partição de **`movie01`**. 

Esse exemplo também define uma expressão de filtro após a linha de comentário 2 que filtra qualquer item que não tenha um valor **`actingschoolname`**.

O `QueryEnhancedRequest` combina a condição da chave e a expressão do filtro para a consulta.

```
    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 – Saída usando a consulta condicional `keyEqual`**  
Esta é a saída gerada pela execução do método. A saída exibe itens com um valor `movieName` de **movie01** e não exibe nenhum item com `actingSchoolName` igual a **`null`**.  

```
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'}
```

#### Consultar itens por chave de partição e chave de classificação
<a name="sort-type-query-conditional-example"></a>

O `sortGreaterThanOrEqualTo` `QueryConditional` refina uma correspondência de chave de partição (**movie01**) adicionando uma condição de chave de classificação para valores maiores ou iguais a **actor2**.

Os [métodos `QueryConditional`](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html) que começam com `sort` exigem que o valor da chave de partição corresponda e refinam ainda mais a consulta por meio de uma comparação com base no valor da chave de classificação. `Sort` no nome do método não significa que os resultados estão classificados, mas que um valor de chave de classificação será usado para comparação.

No trecho a seguir, alteramos a solicitação de consulta mostrada anteriormente após a linha de comentário 3. Esse trecho substitui a condicional de consulta “KeyEqual” pela condicional de consulta "sortGreaterThanOrEqualTo" que foi definida após a linha de comentário 1a. O código a seguir também remove a expressão do filtro.

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

**Example – Saída usando a consulta condicional `sortGreaterThanOrEqualTo`**  
A saída a seguir exibe os resultados da consulta. A consulta retorna itens que têm um valor `movieName` igual a **movie01** e somente itens que têm um valor `actorName` maior ou igual a **actor2**. Como removemos o filtro, a consulta retorna itens que não têm valor para o atributo `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'}
```

# Realizar operações em lote
<a name="ddb-en-client-use-multiop-batch"></a>

A API do Cliente Aprimorado do DynamoDB oferece dois métodos em lote, [`batchGetItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) e [`batchWriteItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchWriteItem(java.util.function.Consumer)).

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

Com o método [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)), você pode recuperar até 100 itens individuais em várias tabelas com uma solicitação geral. O exemplo a seguir usa as classes de dados [`Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) e [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) mostradas anteriormente.

No exemplo após as linhas 1 e 2, você cria objetos `[ReadBatch](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ReadBatch.html)` que são adicionados posteriormente como parâmetros ao método `batchGetItem()` após a linha de comentário 3. 

O código após a linha de comentário 1 cria o lote para ser lido na tabela `Customer`. O código após a linha de comentário 1a mostra o uso de um compilador `[GetItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/GetItemEnhancedRequest.Builder.html)` que usa um valor de chave primária e um valor de chave de classificação para especificar o item a ser lido. Se a classe de dados tiver uma chave composta, você deverá fornecer o valor da chave de partição e o valor da chave de classificação. 

Ao contrário de especificar valores-chave para solicitar um item, você pode usar uma classe de dados para solicitar um item, conforme mostrado após a linha de comentário 1b. O SDK extrai os valores-chave nos bastidores antes de enviar a solicitação.

Ao especificar o item usando a abordagem baseada em chaves, conforme mostrado nas duas instruções após 2a, você também pode especificar que o DynamoDB deve realizar uma [leitura altamente consistente](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html). Quando o método `consistentRead()` é usado, ele deve ser usado em todos os itens solicitados para a mesma tabela.

Para recuperar os itens encontrados pelo DynamoDB, use o método `[resultsForTable() ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#resultsForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` mostrado após a linha de comentário 4. Chame o método para cada tabela que foi lida na solicitação. `resultsForTable()` retorna uma lista de itens encontrados que você pode processar usando qualquer método `java.util.List`. Este exemplo registra cada item.

Para descobrir itens que o DynamoDB não processou, use a abordagem após a linha de comentários 5. A classe `BatchGetResultPage` tem o método `[unprocessedKeysForTable()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#unprocessedKeysForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` que dá acesso a cada chave que não foi processada. A [referência BatchGetItem da API](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) tem mais informações sobre situações que resultam em itens não processados.

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

Suponha que os itens a seguir estejam nas duas tabelas antes de executar o código de exemplo.

### Itens em tabelas
<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'}
```

A saída a seguir mostra os itens retornados e registrados após a linha de comentários 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'}
```

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

O método `batchWriteItem()` coloca ou exclui vários itens em uma ou mais tabelas. Você pode especificar até 25 operações individuais de colocação ou exclusão na solicitação. O exemplo a seguir usa as classes modelo [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) e [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) mostradas anteriormente.

Os objetos `WriteBatch` são construídos após as linhas de comentário 1 e 2. Para a tabela `ProductCatalog`, o código coloca um item e exclui um item. Para a tabela `MovieActor` após a linha de comentário 2, o código coloca dois itens e exclui um.

O método `batchWriteItem` é chamado após a linha de comentário 3. O parâmetro `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteItemEnhancedRequest.Builder.html)` fornece as solicitações em lote para cada tabela.

O objeto `[BatchWriteResult](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteResult.html)` retornado fornece métodos separados para cada operação para visualizar solicitações não processadas. O código após a linha de comentário 4a fornece as chaves para solicitações de exclusão não processadas e o código após a linha de comentário 4b fornece os itens de venda não processados.

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

Os métodos auxiliares a seguir fornecem os objetos de modelo para as operações colocar e excluir.

### Métodos auxiliares
<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.     }
```

Suponha que as tabelas contenham os itens a seguir antes de você executar o código de exemplo.

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

Depois que o código de exemplo for concluído, as tabelas conterão os itens a seguir.

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

Observe na tabela `MovieActor` que o item do filme `Blue Jasmine` foi substituído pelo item usado na solicitação de colocação adquirida por meio do método `getMovieActorBlanchettPartial()` auxiliar. Se um valor de atributo do bean de dados não tiver sido fornecido, o valor no banco de dados será removido. É por isso que o resultado `actingSchoolName` é nulo para o item do filme `Blue Jasmine`.

**nota**  
Embora a documentação da API sugira que expressões de condição possam ser usadas e que a capacidade consumida e as métricas de coleta possam ser retornadas com solicitações individuais de [colocar](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PutItemEnhancedRequest.html) e [excluir](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/DeleteItemEnhancedRequest.html), esse não é o caso em um cenário de gravação em lote. Para melhorar o desempenho das operações em lote, essas opções individuais são ignoradas.

# Realizar operações de transação
<a name="ddb-en-client-use-multiop-trans"></a>

A API do Cliente Aprimorado do DynamoDB fornece o `transactGetItems()` e os métodos `transactWriteItems()`. Os métodos de transação do SDK para Java fornecem atomicidade, consistência, isolamento e durabilidade (ACID) nas tabelas do DynamoDB, ajudando você a manter a correção dos dados em suas aplicações.

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

O método `[transactGetItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactGetItems(java.util.function.Consumer))` aceita até 100 solicitações individuais de itens. Todos os itens são lidos em uma única transação atômica. O *Guia do desenvolvedor do Amazon DynamoDB* tem informações sobre as [condições que causam a falha do método `transactGetItems()`](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txgetitems) e também sobre o nível de isolamento usado ao chamar `[transactGetItem()](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-isolation)`.

Depois da linha de comentário 1 no exemplo a seguir, o código chama o método `transactGetItems()` com um parâmetro `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html)`. O `[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))` do construtor é chamado três vezes com um objeto de dados contendo os valores-chave que o SDK usará para gerar a solicitação final.

A solicitação retorna uma lista de objetos `[Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html)` após a linha de comentário 2. A lista de documentos que é retornada contém instâncias de [Documentos](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html) não nulas dos dados do item na mesma ordem solicitada. O método `[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))` converte um objeto não digitado em um objeto `Document` Java digitado se os dados do item forem retornados, caso contrário, o método retornará nulo.

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

As tabelas do DynamoDB contêm os seguintes itens antes da execução do exemplo de código.

```
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'}
```

A saída a seguir é registrada. Se um item for solicitado, mas não encontrado, ele não será retornado, como é o caso da solicitação do filme nomeado `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'}
```

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

O `[transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))` aceita até 100 ações de colocar, atualizar ou excluir em uma única transação atômica em várias tabelas. O *Guia do desenvolvedor do Amazon DynamoDB *contém detalhes sobre restrições e condições de falha da [operação do serviço subjacente do DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems).

### Exemplo básico
<a name="ddb-en-client-use-multiop-trans-writeitems-basic"></a>

No exemplo a seguir, quatro operações são solicitadas para duas tabelas. As classes de modelo correspondentes [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) e [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) foram mostradas anteriormente.

Cada uma das três operações possíveis: colocar, atualizar e excluir usa um parâmetro de solicitação dedicado para especificar os detalhes. 

O código após a linha de comentário 1 mostra a variação simples do método `addPutItem()`. O método aceita um objeto `[MappedTableResource](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.html)` e a instância do objeto de dados onde colocar. A declaração após a linha de comentário 2 mostra a variação que aceita uma instância `[TransactPutItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactPutItemEnhancedRequest.html)`. Essa variação permite adicionar mais opções na solicitação, como uma expressão de condição. Um [exemplo](#ddb-en-client-use-multiop-trans-writeitems-opcondition) subsequente mostra uma expressão de condição para uma operação individual.

Uma operação de atualização é solicitada após a linha de comentários 3. `[TransactUpdateItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactUpdateItemEnhancedRequest.Builder.html)` tem um método `ignoreNulls()` que permite configurar o que o SDK faz com os valores `null` no objeto modelo. Se o método `ignoreNulls()` retornar true, o SDK não removerá os valores dos atributos da tabela para os atributos do objeto de dados que são `null`. Se o método `ignoreNulls()` retornar false, o SDK solicitará que o serviço do DynamoDB remova os atributos do item na tabela. O valor padrão para `ignoreNulls` é false.

A afirmação após a linha de comentário 4 mostra a variação de uma solicitação de exclusão que usa um objeto de dados. O cliente aprimorado extrai os valores-chave antes de enviar a solicitação final.

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

Os métodos auxiliares a seguir fornecem os objetos de dados para os parâmetros `add*Item`.

#### Métodos auxiliares
<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;
    }
```

As tabelas do DynamoDB contêm os seguintes itens antes da execução do exemplo de código.

```
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'}
```

Os itens a seguir estão nas tabelas após a conclusão da execução do código.

```
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'}
```

O item na linha 2 foi excluído e as linhas 3 e 5 mostram os itens que foram colocados. A linha 4 mostra a atualização da linha 1. O valor `price` é o único valor que foi alterado no item. Se `ignoreNulls()` tivesse retornado false, a linha 4 ficaria como a seguinte linha:

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

### Exemplo de verificação de condição
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond"></a>

O exemplo a seguir mostra o uso de uma verificação de condição. Uma verificação de condição é usada para verificar se um item existe ou para verificar a condição de atributos específicos de um item no banco de dados. O item verificado na verificação de condição não pode ser usado em outra operação na transação.

**nota**  
Você não pode visar o mesmo item com várias operações dentro da mesma transação. Por exemplo, você não pode executar uma verificação de condição e também tentar atualizar o item na mesma transação.

O exemplo mostra um tipo de cada operação em uma solicitação transacional de itens de gravação. Depois da linha de comentário 2, o método `addConditionCheck()` fornece a condição que falha na transação se o parâmetro `conditionExpression` for avaliado como `false`. A expressão de condição que é retornada do método mostrado no bloco de métodos auxiliares verifica se o ano de premiação do filme `Sophie's Choice` não é igual a `1982`. Se for igual, a expressão será avaliada como `false` e a transação falhará.

Este guia discutirá [expressões](ddb-en-client-expressions.md) em profundidade em outro tópico.

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

Os métodos auxiliares a seguir são usados no exemplo de código anterior.

#### Métodos auxiliares
<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;
    }
```

As tabelas do DynamoDB contêm os seguintes itens antes da execução do exemplo de código.

```
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'}
```

Os itens a seguir estão nas tabelas após a conclusão da execução do código.

```
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'}
```

Os itens permanecem inalterados nas tabelas porque a transação falhou. O valor `actingYear` do filme `Sophie's Choice` é `1982`, conforme mostrado na linha 2 dos itens na tabela antes da chamada do método `transactWriteItem()`.

Para capturar as informações de cancelamento da transação, inclua a chamada do método `transactWriteItems()` em um bloco `try` e `catch` o [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). Depois da linha de comentário 4 do exemplo, o código registra cada objeto `[CancellationReason](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CancellationReason.html)`. Como o código após a linha de comentário 3 do exemplo especifica que os valores devem ser retornados para o item que causou a falha da transação, o registro exibe os valores brutos do banco de dados do item do filme `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.)
```

### Exemplo de condição de operação única
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition"></a>

O exemplo a seguir mostra o uso de uma condição em uma única operação em uma solicitação de transação. A operação de exclusão após a linha de comentário 1 contém uma condição que verifica o valor do item de destino da operação em relação ao banco de dados. Neste exemplo, a expressão de condição criada com o método auxiliar após a linha de comentário 2 especifica que o item deve ser excluído do banco de dados se o ano de atuação do filme não for igual a 2013.

As [expressões](ddb-en-client-expressions.md) serão discutidas posteriormente neste guia.

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

Os métodos auxiliares a seguir são usados no exemplo de código anterior.

#### Métodos auxiliares
<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;
    }
```

As tabelas do DynamoDB contêm os seguintes itens antes da execução do exemplo de código.

```
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'}
```

Os itens a seguir estão nas tabelas após a conclusão da execução do código.

```
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'}
```

Os itens permanecem inalterados nas tabelas porque a transação falhou. O valor `actingYear` do filme `Blue Jasmine` é `2013` como mostrado na linha 2 da lista de itens antes da execução do código de exemplo.

As linhas a seguir são registradas no console.

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

# Usar índices secundários
<a name="ddb-en-client-use-secindex"></a>

Os índices secundários melhoram o acesso aos dados definindo chaves alternativas que você usa nas operações de consulta e verificação. Índices secundários globais (GSI) têm uma chave de partição e uma chave de classificação que podem ser diferentes daquelas contidas na tabela base. Por outro lado, os índices secundários locais (LSI) usam a chave de partição do índice primário.

## Anotar a classe de dados com anotações de índice secundário
<a name="ddb-en-client-use-secindex-annomodel"></a>

Os atributos que participam de índices secundários exigem a anotação `@DynamoDbSecondaryPartitionKey` ou `@DynamoDbSecondarySortKey`.

A classe a seguir mostra anotações para dois índices. O GSI nomeado *SubjectLastPostedDateIndex*usa o `Subject` atributo para a chave de partição e o `LastPostedDateTime` para a chave de classificação. O LSI nomeado *ForumLastPostedDateIndex*usa o `ForumName` como chave de partição e `LastPostedDateTime` como chave de classificação.

Observe que o atributo `Subject` tem uma função dupla. É a chave de classificação da chave primária e a chave de partição do GSI nomeado *SubjectLastPostedDateIndex*.

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

A classe `MessageThread` é adequada para ser usada como uma classe de dados para a [tabela Thread de exemplo](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AppendixSampleTables.html) no *Guia de desenvolvedor do Amazon DynamoDB*.

#### Importações
<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 +
                '}';
    }
}
```

## Criar o índice
<a name="ddb-en-client-use-secindex-confindex"></a>

A partir da versão 2.20.86 do SDK para Java, o método `createTable()` gera automaticamente índices secundários a partir de anotações de classes de dados. Por padrão, todos os atributos da tabela base são copiados para um índice e os valores de throughput provisionados são 20 unidades de capacidade de leitura e 20 unidades de capacidade de gravação.

No entanto, se você usar uma versão do SDK anterior à 2.20.86, precisará criar o índice junto com a tabela, conforme mostrado no exemplo a seguir. Este exemplo cria os dois índices para a tabela `Thread`. O parâmetro [builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html) tem métodos para configurar os dois tipos de índices, conforme mostrado após as linhas de comentário 1 e 2. Você usa o método `indexName()` do construtor de índices para associar os nomes de índice especificados nas anotações da classe de dados ao tipo de índice pretendido.

Esse código configura todos os atributos da tabela para terminam nos dois índices após as linhas de comentário 3 e 4. Mais informações sobre [projeções de atributos](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.Projections) estão disponíveis no *Guia do desenvolvedor do 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))
                )
        );
```

## Consultar usando um índice
<a name="ddb-en-client-use-secindex-query"></a>

O exemplo a seguir consulta o índice secundário local *ForumLastPostedDateIndex*.

Seguindo a linha de comentário 2, você cria um [QueryConditional](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)objeto que é necessário ao chamar o método [DynamoDbIndex.query ()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbIndex.html#query(java.util.function.Consumer)). 

Você obtém uma referência ao índice que deseja consultar após a linha de comentário 3 ao passar o nome do índice. Seguindo a linha de comentário 4, você chama o método `query()` no índice que passa o objeto `QueryConditional`. 

Você também configura a consulta para retornar três valores de atributos, conforme mostrado após a linha de comentário 5. Se `attributesToProject()` não for chamado, a consulta retornará todos os valores dos atributos. Observe que os nomes dos atributos especificados começam com letras minúsculas. Esses nomes de atributos correspondem aos usados na tabela, não necessariamente aos nomes dos atributos da classe de dados.

Seguindo a linha de comentário 6, revise os resultados e registre cada item retornado pela consulta e os armazene na lista para retornar ao chamador.

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

Os itens a seguir existem no banco de dados antes da execução da consulta.

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

As instruções de registro nas linhas 1 e 6 resultam na seguinte saída do console:

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

A consulta retornou itens com um valor `forumName` de *Forum02* e um valor `lastPostedDateTime` maior ou igual a *2023.03.31*. Os resultados mostram valores `message` com uma cadeia de caracteres vazia, embora os atributos `message` tenham valores no índice. Isso ocorre porque o atributo da mensagem não foi projetado pelo código após a linha de comentário 5. 