

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Trabaja con DynamoDB
<a name="examples-dynamodb"></a>

[DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html) es un servicio de bases de datos NoSQL totalmente administrado que proporciona un rendimiento rápido y predecible, así como una perfecta escalabilidad. En esta sección se muestra cómo trabajar con DynamoDB mediante la versión AWS SDK para Java 2.x.

## Selección de un cliente de DynamoDB
<a name="ddb-clients-overview"></a>

El SDK ofrece dos métodos principales para trabajar con DynamoDB:

Cliente de bajo nivel (`DynamoDbClient`)  
Proporciona acceso directo a las operaciones de DynamoDB con control total sobre las solicitudes y las respuestas. Utilice este cliente cuando necesite un control detallado o trabaje con esquemas dinámicos.

Cliente mejorado (`DynamoDbEnhancedClient`)  
Ofrece programación orientada a objetos con asignación automática entre objetos de Java y elementos de DynamoDB. También proporciona funciones orientadas a documentos para trabajar con datos tipo JSON que no siguen un esquema fijo. Utilice este cliente cuando trabaje con modelos de datos bien definidos o datos de tipo documento.

## Configuración de clientes de DynamoDB
<a name="ddb-configuration-setup"></a>

Antes de trabajar con DynamoDB, configure el cliente para obtener un rendimiento y una fiabilidad óptimos.

### Descripción del comportamiento de reintento de DynamoDB
<a name="ddb-retry-behavior"></a>

Los clientes de DynamoDB utilizan un número máximo de reintentos predeterminado de 8, que es mayor que el de otros clientes de Servicio de AWS . Este mayor número de reintentos ayuda a administrar la naturaleza distribuida y las limitaciones temporales de capacidad de DynamoDB. Para obtener más información sobre estrategias de reintento, consulte [Configure el comportamiento de reintento en el AWS SDK for Java 2.x](retry-strategy.md).

### Optimización del rendimiento con puntos de conexión basados en cuentas
<a name="ddb-account-based-endpoints-v2"></a>

DynamoDB [AWS ofrece puntos de enlace basados en cuentas](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.SDKOverview.html#Programming.SDKs.endpoints) que mejoran el rendimiento mediante el uso de AWS su ID de cuenta para agilizar el enrutamiento de solicitudes. 

Para utilizar esta característica, debe utilizar la versión 2.28.4 o superior de AWS SDK for Java 2.x. Encontrará la versión más reciente en el [Repositorio central de Maven](https://central.sonatype.com/artifact/software.amazon.awssdk/bom/versions). Las versiones del SDK compatibles utilizan automáticamente los nuevos puntos de conexión.

Para excluir el enrutamiento basado en cuentas, elija una de estas opciones:
+ Configure un cliente de servicio de DynamoDB con `AccountIdEndpointMode` establecido en `DISABLED`.
+ Establezca una variable de entorno.
+ Establezca una propiedad del sistema JVM.
+ Actualice la configuración del archivo de configuración compartido. AWS 

El siguiente ejemplo muestra cómo deshabilitar el enrutamiento basado en cuentas mediante la configuración de un cliente de servicio de DynamoDB:

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

Para obtener más información sobre las demás opciones de configuración, consulte los [puntos finales basados en cuentas](https://docs.aws.amazon.com/sdkref/latest/guide/feature-account-endpoints.html) en la Guía de referencia de herramientas AWS SDKs y herramientas.

## Qué se aborda en este tema
<a name="ddb-topics-overview"></a>

En las secciones siguientes se muestra cómo se trabaja con DynamoDB:
+ [Trabajar con tablas en DynamoDB](examples-dynamodb-tables.md): crear, describir, actualizar y eliminar tablas
+ [Trabaja con elementos en DynamoDB](examples-dynamodb-items.md): añadir, recuperar y actualizar elementos individuales
+ [Asigne objetos Java a elementos de DynamoDB con AWS SDK for Java 2.x](dynamodb-enhanced-client.md): utilizar la asignación de objetos y datos orientados a documentos con el Cliente mejorado

Para ver más ejemplos de código de DynamoDB, consulte Ejemplos de código de [DynamoDB en la biblioteca de ejemplos](java_dynamodb_code_examples.md) de código. AWS 

# Trabajar con tablas en DynamoDB
<a name="examples-dynamodb-tables"></a>

Las tablas son los contenedores de todos los elementos de una base de datos de DynamoDB. Para poder añadir o eliminar datos de DynamoDB, debe crear una tabla.

Para cada tabla, debe definir:
+ Un *nombre* de tabla que sea único para su cuenta y región.
+ Una *clave principal* para la que cada valor debe ser único; no puede haber dos elementos de la tabla que tengan el mismo valor de clave principal.

  Una clave principal puede ser *simple*, formada por una sola clave de partición (HASH) o *compuesta*, formada por una clave de partición y una clave de ordenación (RANGE).

  Cada valor de clave tiene un *tipo de datos* asociado, enumerado por la clase [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html). El valor de clave puede ser binario (B), numérico (N) o una cadena (S). Para obtener más información, consulte [Reglas de nomenclatura y tipos de datos](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html) en la Guía para desarrolladores de Amazon DynamoDB.
+  El *desempeño aprovisionado* son valores que definen el número de unidades de capacidad de lectura/escritura reservadas para la tabla.
**nota**  
 [Los precios de Amazon DynamoDB](https://aws.amazon.com/dynamodb/pricing/) se basan en los valores de desempeño aprovisionado que puede definir en sus tablas para que solo se reserve la capacidad que piensa que va a necesitar para la tabla.

  El desempeño aprovisionado para una tabla se puede modificar en cualquier momento, por lo que puede ajustar la capacidad cuando cambien sus necesidades.

## Creación de una tabla
<a name="dynamodb-create-table"></a>

Use el método `createTable` de `DynamoDbClient’s` para crear una nueva tabla de DynamoDB. Debe crear los atributos de la tabla y un esquema de tabla, que se pueden usar para identificar la clave principal de la tabla. También debe proporcionar los valores iniciales de desempeño aprovisionado y el nombre de una tabla.

**nota**  
Si ya existe una tabla con el nombre elegido, se producirá una `[DynamoDbException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html)`.

### Crear una tabla con una clave principal simple
<a name="dynamodb-create-table-simple"></a>

Este código crea una tabla con un atributo que es la clave principal simple de la tabla. El ejemplo utiliza los objetos `[AttributeDefinition](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeDefinition.html)` y `[KeySchemaElement](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/KeySchemaElement.html)` y para la [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html).

 **Importaciones** 

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

 **Código de** 

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

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

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

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

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

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

Consulte el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTable.java) en GitHub.

### Crear una tabla con una clave primaria compuesta
<a name="dynamodb-create-table-composite"></a>

En el siguiente ejemplo se crea una tabla con dos atributos. Ambos atributos se utilizan para la clave primaria compuesta.

 **Importaciones** 

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

 **Código de** 

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

       String tableId = "";

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

Consulte el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTableCompositeKey.java) en GitHub.

## Lista de tablas
<a name="dynamodb-list-tables"></a>

Puede mostrar las tablas de una región determinada llamando al método `listTables` de `DynamoDbClient’s`.

**nota**  
Si la tabla designada no existe para su cuenta y región, se produce una [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html).

 **Importaciones** 

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

 **Código de** 

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

        boolean moreTables = true;
        String lastName = null;

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

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

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

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

De forma predeterminada, se devuelve un máximo de 100 tablas para cada llamada: utilice `lastEvaluatedTableName` en el objeto [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html) devuelto para obtener la última tabla que se evaluó. Puede utilizar este valor para iniciar la enumeración después del último valor devuelto de la enumeración anterior.

Consulte el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/ListTables.java) en GitHub.

## Describir una tabla (obtener información de ella)
<a name="dynamodb-describe-table"></a>

Utilice el método `describeTable` de `DynamoDbClient’s` para obtener información sobre una tabla.

**nota**  
Si la tabla designada no existe para su cuenta y región, se produce una [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html).

 **Importaciones** 

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

 **Código de** 

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

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

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

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

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

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

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

Consulte el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DescribeTable.java) en GitHub.

## Modificar (actualizar) una tabla
<a name="dynamodb-update-table"></a>

Puede modificar los valores de rendimiento aprovisionado de la tabla en cualquier momento llamando al método `updateTable` de `DynamoDbClient’s`.

**nota**  
Si la tabla designada no existe para su cuenta y región, se produce una [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html).

 **Importaciones** 

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

 **Código de** 

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

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

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

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

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

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

Consulte el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateTable.java) en GitHub.

## Eliminación de una tabla
<a name="dynamodb-delete-table"></a>

Para eliminar una tabla, llame al método `deleteTable` de `DynamoDbClient’s` y proporcione el nombre de la tabla.

**nota**  
Si la tabla designada no existe para su cuenta y región, se produce una [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html).

 **Importaciones** 

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

 **Código de** 

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

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

        try {
            ddb.deleteTable(request);

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

Consulte el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteTable.java) en GitHub.

## Más información
<a name="more-information"></a>
+  [Prácticas recomendadas para trabajar con tablas](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html) en la Guía para desarrolladores de Amazon DynamoDB
+  [Trabajar con tablas en DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html) en la Guía para desarrolladores de Amazon DynamoDB

# Trabaja con elementos en DynamoDB
<a name="examples-dynamodb-items"></a>

*En DynamoDB, un elemento es una colección de *atributos*, cada uno de los cuales tiene un *nombre* y un valor.* Los valores de los atributos pueden ser escalares, conjuntos o tipos de documentos. Para obtener más información, consulte [Reglas de nomenclatura y tipos de datos](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html) en la Guía para desarrolladores de Amazon DynamoDB .

## Recuperar (obtener) un elemento de una tabla
<a name="dynamodb-get-item"></a>

Llama al `getItem` método DynamoDbClient's y pásale un [GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html)objeto con el nombre de la tabla y el valor de la clave principal del elemento que desees. Devuelve un [GetItemResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemResponse.html)objeto con todos los atributos de ese elemento. Puede especificar una o varias [expresiones de proyección](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html) en `GetItemRequest` para recuperar atributos específicos.

Puede utilizar el `item()` método del `GetItemResponse` objeto devuelto para recuperar un [mapa](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html) de los pares clave (cadena [AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html)) y valor () asociados al elemento.

 **Importaciones** 

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

 **Código** 

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

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

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

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

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

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

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

Consulta el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/GetItem.java) en GitHub.

## Recuperar (obtener) un elemento de una tabla usando el cliente asíncrono
<a name="id1ddb"></a>

Invoque el `getItem` método del DynamoDbAsyncClient y pásele un [GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html)objeto con el nombre de la tabla y el valor de la clave principal del elemento que desee.

Puede devolver una instancia de [recopilación](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Collection.html) con todos los atributos de ese elemento (consulte el siguiente ejemplo).

 **Importaciones** 

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

 **Código** 

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

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

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

        try {

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

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

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

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

Consulte el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodbasync/src/main/java/com/example/dynamodbasync/DynamoDBAsyncGetItem.java) en. GitHub

## Agregar un nuevo elemento a una tabla
<a name="dynamodb-add-item"></a>

Cree un [mapa](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html) de pares de clave-valor que represente los atributos del elemento. Estos deben incluir valores para los campos de la clave principal de la tabla. Si el elemento identificado por la clave principal ya existe, la solicitud *actualiza* sus campos.

**nota**  
Si la tabla con el nombre indicado no existe para tu cuenta y región, [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)aparecerá una.

 **Importaciones** 

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

 **Código** 

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

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

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

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

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

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

Consulta el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/PutItem.java) en GitHub.

## Actualizar un elemento existente en una tabla
<a name="dynamodb-update-item"></a>

Puede actualizar un atributo de un elemento que ya existe en una tabla mediante el método `updateItem` de DynamoDbClient, proporcionando el nombre de la tabla, el valor de clave principal y un mapa de los campos que se van a actualizar.

**nota**  
Si la tabla con el nombre asignado no existe para tu cuenta y región, o si el elemento identificado con la clave principal que has introducido no existe, [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)aparecerá un.

 **Importaciones** 

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

 **Código** 

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

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

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

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

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

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

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

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

Consulta el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateItem.java) en GitHub.

## Eliminar un elemento existente en una tabla
<a name="dynamodb-delete-item"></a>

Puede eliminar un elemento que existe en una tabla utilizando el `deleteItem` método DynamoDbClient's y proporcionando un nombre de tabla, así como el valor de la clave principal.

**nota**  
Si la tabla con el nombre asignado no existe para tu cuenta y región, o si el elemento identificado con la clave principal que has introducido no existe, [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)se generará una.

 **Importaciones** 

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

 **Código** 

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

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

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

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

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

Consulta el [ejemplo completo](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteItem.java) en GitHub.

## Más información
<a name="more-information"></a>
+  [Prácticas recomendadas para el uso de elementos](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/best-practices.html) en la Guía para desarrolladores de Amazon DynamoDB 
+  [Cómo trabajar con los elementos DynamoDB](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/WorkingWithItems.html) de la guía para Amazon DynamoDB desarrolladores

# Asigne objetos Java a elementos de DynamoDB con AWS SDK for Java 2.x
<a name="dynamodb-enhanced-client"></a>

La [API de cliente mejorado de DynamoDB](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html) es una biblioteca de alto nivel, sucesora de la clase `DynamoDBMapper` del SDK para Java v1.x. Ofrece una forma sencilla de asignar clases del cliente a tablas de DynamoDB. Defina las relaciones entre tablas y sus correspondientes clases de datos en el código. Después de definir estas relaciones, puede realizar intuitivamente varias operaciones de creación, lectura, actualización o eliminación (CRUD) en tablas o elementos en DynamoDB.

La API de cliente mejorado de DynamoDB también incluye [la API de documentos mejorada](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html) para trabajar con elementos de tipo documento que no siguen un esquema definido. 

**Topics**
+ [Empiece a utilizar la API de cliente mejorado de DynamoDB](ddb-en-client-getting-started.md)
+ [Aprenda los conceptos básicos de la API de cliente mejorado de DynamoDB](ddb-en-client-use.md)
+ [Utilizar características de asignación avanzadas](ddb-en-client-adv-features.md)
+ [Trabaje con documentos JSON con la API de documentos mejorada para DynamoDB](ddb-en-client-doc-api.md)
+ [Uso de extensiones para personalizar operaciones de DynamoDB Enhanced Client](ddb-en-client-extensions.md)
+ [Utilizar la API de cliente mejorado de DynamoDB de forma asíncrona](ddb-en-client-async.md)
+ [Anotaciones de clases de datos](ddb-en-client-anno-index.md)

# Empiece a utilizar la API de cliente mejorado de DynamoDB
<a name="ddb-en-client-getting-started"></a>

El siguiente tutorial presenta los aspectos básicos necesarios para trabajar con la API de cliente mejorado de DynamoDB.

## agregar dependencias de API
<a name="ddb-en-client-gs-dep"></a>

Para empezar a trabajar con la API de cliente mejorado de DynamoDB en su proyecto, añada una dependencia al artefacto de Maven `dynamodb-enhanced`. Esto se muestra en el ejemplo siguiente. 

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

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

Realice una búsqueda en el repositorio central de Maven para encontrar la [última versión](https://central.sonatype.com/artifact/software.amazon.awssdk/bom) y *<VERSION>* sustitúyala por este valor.

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

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

Realice una búsqueda en el repositorio central de Maven para encontrar la [última versión](https://central.sonatype.com/artifact/software.amazon.awssdk/bom) y sustitúyala por este *<VERSION>* valor.

------

# Generación de un `TableSchema` partir de una clase de datos
<a name="ddb-en-client-gs-tableschema"></a>

Un `[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` permite al cliente mejorado asignar valores de atributos de DynamoDB hacia y desde las clases del cliente. En este tutorial, conocerá los `TableSchema` derivados de una clase de datos estáticos y generadas a partir de código mediante un generador.

## Utilice una clase de datos anotada
<a name="ddb-en-client-gs-tableschema-anno-bean"></a>

El SDK para Java 2.x incluye un [conjunto de anotaciones](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/package-summary.html) que puede utilizar con una clase de datos para generar rápidamente un `TableSchema` para asignar sus clases a tablas.

Comience por crear una clase de datos que se ajuste a la [JavaBean especificación](https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf). La especificación requiere que una clase tenga un constructor público sin argumentos, además de getters y setters para cada atributo de la clase. Incluya una anotación a nivel de clase para indicar que la clase de datos es una `DynamoDbBean`. Además, como mínimo, incluya una anotación `DynamoDbPartitionKey` en el getter o setter para el atributo de clave principal. 

Puede aplicar [anotaciones de nivel de atributo](ddb-en-client-anno-index.md) a getters o setters, pero no a ambos.

**nota**  
El término `property` se usa normalmente para un valor encapsulado en un. JavaBean Sin embargo, en esta guía se utiliza el término `attribute` en su lugar para mantener la coherencia con la terminología utilizada por DynamoDB.

La clase siguiente `Customer` muestra anotaciones que vinculan la definición de clase a una tabla de DynamoDB.

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

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

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

import java.time.Instant;

@DynamoDbBean
public class Customer {

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

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

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

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

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

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

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

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

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

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

Una vez creada una clase de datos anotada, utilícela para crear el `TableSchema`, como se muestra en el siguiente fragmento.

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

Un `TableSchema` se diseña para ser estático e inmutable. Por lo general, puede instanciarlo en el momento de cargar la clase.

El método de fábrica estático `TableSchema.fromBean()` introspecciona el objeto bean para generar la asignación de atributos (propiedades) de la clase de datos a y desde los atributos de DynamoDB.

Para ver un ejemplo de cómo trabajar con un modelo de datos compuesto por varias clases de datos, consulte la clase `Person` en la sección [Uso de atributos que son beans, mapas, listas y conjuntos](ddb-en-client-adv-features-nested.md).

## Uso de un constructor
<a name="ddb-en-client-gs-tableschema-builder"></a>

Puede saltarse el coste de la introspección de objetos bean si define el esquema de la tabla en código. Si codificas el esquema, no es necesario que tu clase siga los estándares de JavaBean nomenclatura ni que esté anotada. El siguiente ejemplo utiliza un constructor y es equivalente al ejemplo de clase `Customer` que utiliza anotaciones.

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

# Crear un cliente mejorado y una `DynamoDbTable`
<a name="ddb-en-client-getting-started-dynamodbTable"></a>

## Crear un cliente mejorado
<a name="ddb-en-client-getting-started-dynamodbTable-eclient"></a>

La [DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html)clase o su contraparte asíncrona [DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html), es el punto de partida para trabajar con la API de cliente mejorada de DynamoDB.

El cliente mejorado requiere un `[DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html)` estándar para hacer el trabajo. La API ofrece dos formas de crear una instancia de `DynamoDbEnhancedClient`. La primera opción, que se muestra en el siguiente fragmento, crea un `DynamoDbClient` estándar con los ajustes predeterminados tomados de la configuración.

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

Si desea configurar el cliente estándar subyacente, puede suministrarlo al método constructor del cliente mejorado como se muestra en el siguiente fragmento.

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

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

## Crear una instancia de `DynamoDbTable`
<a name="ddb-en-client-getting-started-dynamodbTable-table"></a>

Piense en una [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) como la representación del cliente de una tabla de DynamoDB que utiliza la funcionalidad de mapeo proporcionada por un `TableSchema`. La clase `DynamoDbTable` proporciona métodos para las operaciones CRUD que permiten interactuar con una sola tabla de DynamoDB.

`DynamoDbTable<T>` es una clase genérica que toma un único argumento de tipo, ya sea una clase personalizada o un `EnhancedDocument` cuando se trabaja con elementos de tipo documento. Este tipo de argumento establece la relación entre la clase que utiliza y la tabla única de DynamoDB.

Siga el método de fábrica `table()` del `DynamoDbEnhancedClient` para crear una instancia `DynamoDbTable`, como se muestra en el siguiente fragmento.

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

Las instancias de `DynamoDbTable` son aptas para ser únicas porque son inmutables y se pueden usar en toda la aplicación.

El código ahora tiene una representación en memoria de una tabla de DynamoDB que puede funcionar con instancias `Customer`. La tabla de DynamoDB real puede existir o no. Si la tabla nombrada `Customer` ya existe, puede empezar a realizar operaciones de CRUD en ella. Si no existe, utilice la instancia de `DynamoDbTable` para crear la tabla, tal y como se explica en la siguiente sección.

# Cree una tabla de DynamoDB si es necesario
<a name="ddb-en-client-gs-ddbtable"></a>

Después de crear una instancia `DynamoDbTable`, úsela para crear una tabla de DynamoDB por *única vez*.

## Crear código de ejemplo de tabla
<a name="ddb-en-client-gs-ddbtable-createex"></a>

En el siguiente ejemplo, se crea una tabla de DynamoDB basada en la clase de datos `Customer`. 

En este ejemplo se crea una tabla DynamoDB con el nombre `Customer` —idéntico al nombre de la clase— pero el nombre de la tabla puede ser otro. Sea cual sea el nombre que dé a la tabla, debe usar este nombre en otras aplicaciones para trabajar con la tabla. Proporcione este nombre al método `table()` cada vez que cree otro objeto `DynamoDbTable` para trabajar con la tabla de DynamoDB subyacente.

El parámetro lambda de Java, `builder`, que se pasa al método `createTable`, permite [personalizar la tabla](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html). En este ejemplo, se configura el [rendimiento aprovisionado](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.ProvisionedThroughput.Manual). Si desea utilizar la configuración predeterminada al crear una tabla, omita el generador, tal y como se muestra en el siguiente fragmento.

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

Cuando se utiliza la configuración predeterminada, no se establecen los valores del rendimiento aprovisionado. En su lugar, el modo de facturación de la tabla se establece como [bajo demanda](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.OnDemand).

En el ejemplo también se utiliza una `[DynamoDbWaiter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/waiters/DynamoDbWaiter.html)` antes de intentar imprimir el nombre de la tabla recibido en la respuesta. Crear una tabla lleva algún tiempo. Por lo tanto, utilizar un 'waiter' significa que no tiene que escribir lógica que consulte el servicio de DynamoDB para verificar si la tabla existe antes de su uso.

### Importaciones
<a name="ddb-en-client-gs-ddbtable-imports"></a>

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

### Código
<a name="ddb-en-client-gs-ddbtable-code"></a>

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

**nota**  
Los nombres de los atributos de una tabla de DynamoDB comienzan con una letra minúscula cuando la tabla se genera a partir de una clase de datos. Si desea que el nombre del atributo de la tabla comience con una letra mayúscula, utilice la [anotación `@DynamoDbAttribute(NAME)`](ddb-en-client-adv-features-inex-attr.md) y proporcione el nombre que desee como parámetro.

# Realizar operaciones
<a name="ddb-en-client-gs-use"></a>

Una vez creada la tabla, utilice la instancia `DynamoDbTable` para las operaciones en la tabla de DynamoDB. 

En el siguiente ejemplo, se pasa un singleton `DynamoDbTable<Customer>` como parámetro junto con una [instancia de clase de datos `Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) para añadir un nuevo elemento a la tabla.

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

## Objeto `Customer`
<a name="perform_ops_create_customer_instatnce"></a>

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

Antes de enviar el objeto `customer` al servicio DynamoDB, registre el resultado del método del objeto `toString()` para compararlo con lo que envía el cliente mejorado.

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

El registro a nivel de cable muestra la carga útil de la solicitud generada. El cliente mejorado generó la representación de bajo nivel a partir de la clase de datos. El atributo `regDate`, que es un tipo `Instant` en Java, se representa como una cadena de DynamoDB.

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

# Trabajar con una tabla existente
<a name="ddb-en-client-gs-existingtable"></a>

En la sección anterior se muestra cómo crear una tabla de DynamoDB a partir de una clase de datos de Java. Si ya tiene una tabla existente y desea utilizar las características del cliente mejorado, puede crear una clase de datos de Java para que funcione con la tabla. Debe examinar la tabla de DynamoDB y añadir las anotaciones necesarias a la clase de datos. 

Antes de trabajar con una tabla existente, llame al método `DynamoDbEnhanced.table()`. Esto se hizo en el ejemplo anterior con la instrucción siguiente.

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

Una vez devuelta la instancia `DynamoDbTable`, puede empezar a trabajar de inmediato con la tabla subyacente. No es necesario volver a crear la tabla llamando al método `DynamoDbTable.createTable()`.

El siguiente ejemplo lo demuestra mediante la recuperación inmediata de una instancia `Customer` de la tabla de DynamoDB.

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

**importante**  
El nombre de la tabla utilizado en el método `table()` debe coincidir con el nombre de la tabla de DynamoDB existente.

# Aprenda los conceptos básicos de la API de cliente mejorado de DynamoDB
<a name="ddb-en-client-use"></a>

Este tema trata las características básicas de la API de cliente mejorado de DynamoDB y la compara con la [API de cliente de DynamoDB estándar](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html).

Si es la primera vez que utiliza la API de cliente mejorado de DynamoDB, le recomendamos que consulte el [tutorial introductorio](ddb-en-client-getting-started.md) para familiarizarse con las clases fundamentales.

## Elementos de DynamoDB en Java
<a name="ddb-en-client-use-usecase"></a>

Las tablas de DynamoDB almacenan elementos. Según el caso práctico, los elementos del lado de Java pueden adoptar la forma de datos estructurados de forma estática o estructuras creadas dinámicamente. 

Si su caso requiere elementos con un conjunto coherente de atributos, utilice [clases anotadas](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean) o un [constructor](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder) para generar los tipos estáticos adecuados de `TableSchema`. 

Alternativamente, si necesita almacenar elementos que consten de estructuras variables, cree un `DocumentTableSchema`. `DocumentTableSchema` forma parte de la [API de documento mejorada](ddb-en-client-doc-api.md) y solo requiere una clave principal de tipo estático y funciona con instancias `EnhancedDocument` para contener los elementos de datos. La API de documentos mejorada se trata en otro [tema](ddb-en-client-doc-api.md).

## Tipos de atributos para clases de modelos de datos
<a name="ddb-en-client-use-types"></a>

Si bien DynamoDB admite [un número menor de tipos de atributos](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes) en comparación con el sistema de tipos enriquecidos de Java, la API de cliente mejorado de DynamoDB proporciona mecanismos para convertir los miembros de una clase de Java a y desde tipos de atributos de DynamoDB.

Los tipos de atributos (propiedades) de las clases de datos de Java deben ser tipos de objetos, no primitivos. Por ejemplo, utilice siempre tipos de datos de objetos `Long` y `Integer`, no primitivos `long` y `int`.

[De forma predeterminada, la API de cliente mejorada de DynamoDB admite convertidores de atributos para una gran cantidad de tipos, [como](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html) Integer [[BigDecimal](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/BigDecimalAttributeConverter.html),](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html) String e Instant.](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/InstantAsStringAttributeConverter.html) La lista aparece en las [clases de implementación conocidas de la AttributeConverter interfaz](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html). La lista incluye muchos tipos y colecciones, como mapas, listas y conjuntos.

Para almacenar los datos de un tipo de atributo que no se admite de forma predeterminada o que no se ajusta a la JavaBean convención, puede escribir una `AttributeConverter` implementación personalizada para realizar la conversión. Consulte la sección sobre conversión de atributos para ver un [ejemplo](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-example).

Para almacenar los datos de un tipo de atributo cuya clase cumpla con la especificación de beans de Java (o una [clase de datos inmutable](ddb-en-client-use-immut.md)), puede adoptar dos enfoques. 
+ Si tiene acceso al archivo fuente, puede anotar la clase con `@DynamoDbBean` (o `@DynamoDbImmutable`). La sección que analiza los atributos anidados muestra [ejemplos](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-anno) del uso de clases anotadas.
+ Si no tiene acceso al archivo de origen de la clase de JavaBean datos del atributo (o no quiere hacer anotaciones en el archivo de origen de una clase a la que sí tiene acceso), puede utilizar el enfoque de creación. Esto crea un esquema de tabla sin definir las claves. A continuación, puede anidar este esquema de tabla dentro de otro esquema de tabla para realizar el mapeo. La sección de atributos anidados contiene un [ejemplo](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-builder) que muestra el uso de esquemas anidados.

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

Al utilizar el método `putItem`, el cliente mejorado no incluye los atributos con valores nulos de un objeto de datos mapeado en la solicitud a DynamoDB.

El comportamiento predeterminado del SDK para las solicitudes `updateItem` elimina atributos del elemento de DynamoDB que están configurados como nulos en el objeto que se envía en el método `updateItem`. Si desea actualizar algunos valores de atributos y mantener los demás sin cambios, tiene dos opciones.
+ Recupere el elemento (usando `getItem`) antes de realizar cambios en los valores. Con este método, el SDK envía todos los valores antiguos y actualizados a DynamoDB.
+ Utilice `[IgnoreNullsMode](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html).SCALAR_ONLY` o `IgnoreNullsMode.MAPS_ONLY` cuando cree la solicitud para actualizar el elemento. Ambos modos ignoran las propiedades con valores nulos del objeto que representan atributos escalares en DynamoDB. El tema [Actualización de elementos que contienen tipos complejos](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-updates) de esta guía contiene más información sobre los valores `IgnoreNullsMode` y sobre cómo trabajar con tipos complejos.

El siguiente ejemplo demuestra `ignoreNullsMode()` para el 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 del cliente mejorado de DynamoDB
<a name="ddb-en-client-use-basic-ops"></a>

Los métodos básicos del cliente mejorado se corresponden con las operaciones del servicio de DynamoDB que les dan nombre. Los siguientes ejemplos muestran la variación más simple de cada método. Puede personalizar cada método pasando un objeto de solicitud mejorado. Los objetos de solicitud mejorados ofrecen la mayoría de las características disponibles en el cliente estándar de DynamoDB. Estas acciones se documentan por completo en la Referencia de la API de AWS SDK for Java 2.x .

El ejemplo usa la [Clase `Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) mostrada 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 el cliente mejorado con el cliente estándar de DynamoDB
<a name="ddb-en-client-use-compare"></a>

Los dos APIs clientes de DynamoDB [(](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html)estándar [y](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html) mejorado) le permiten trabajar con tablas de DynamoDB para realizar operaciones CRUD (crear, leer, actualizar y eliminar) a nivel de datos. La diferencia entre los clientes reside en la forma en que se logra. APIs Con el cliente estándar, se trabaja directamente con atributos de datos de bajo nivel. La API de cliente mejorada utiliza clases de Java conocidas y se asigna a la API de bajo nivel en segundo plano.

Si bien ambos clientes APIs admiten operaciones a nivel de datos, el cliente estándar de DynamoDB también admite operaciones a nivel de recursos. Las operaciones a nivel de recursos gestionan la base de datos, como la creación de copias de seguridad, la creación de listas y la actualización de tablas. La API de cliente mejorada admite un número selecto de operaciones a nivel de recursos, como la creación, descripción y eliminación de tablas.

Para ilustrar los diferentes enfoques utilizados por los dos clientes APIs, los siguientes ejemplos de código muestran la creación de la misma `ProductCatalog` tabla con el cliente estándar y el cliente mejorado.

### Comparación: crear una tabla mediante el cliente estándar de DynamoDB
<a name="ddb-en-client-use-compare-cs1"></a>

```
DependencyFactory.dynamoDbClient().createTable(builder -> builder
        .tableName(TABLE_NAME)
        .attributeDefinitions(
                b -> b.attributeName("id").attributeType(ScalarAttributeType.N),
                b -> b.attributeName("title").attributeType(ScalarAttributeType.S),
                b -> b.attributeName("isbn").attributeType(ScalarAttributeType.S)
        )
        .keySchema(
                builder1 -> builder1.attributeName("id").keyType(KeyType.HASH),
                builder2 -> builder2.attributeName("title").keyType(KeyType.RANGE)
        )
        .globalSecondaryIndexes(builder3 -> builder3
                        .indexName("products_by_isbn")
                        .keySchema(builder2 -> builder2
                                .attributeName("isbn").keyType(KeyType.HASH))
                        .projection(builder2 -> builder2
                                .projectionType(ProjectionType.INCLUDE)
                                .nonKeyAttributes("price", "authors"))
                        .provisionedThroughput(builder4 -> builder4
                                .writeCapacityUnits(5L).readCapacityUnits(5L))
        )
        .provisionedThroughput(builder1 -> builder1
                .readCapacityUnits(5L).writeCapacityUnits(5L))
);
```

### Comparación: crear una tabla mediante el cliente mejorado de 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))
        )
);
```

El cliente mejorado utiliza la siguiente clase de datos anotados. El cliente mejorado de DynamoDB asigna los tipos de datos de Java a los tipos de datos de DynamoDB para obtener un código menos detallado que sea más fácil de seguir. `ProductCatalog` es un ejemplo del uso de una clase inmutable con el cliente mejorado de DynamoDB. El uso de clases inmutables para las clases de datos mapeados [se analiza más adelante en este tema](ddb-en-client-use-immut.md).

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

Los dos ejemplos de código siguientes de una escritura por lotes ilustran la imprecisión y la falta de seguridad de tipos cuando se utiliza el cliente estándar frente al cliente mejorado.

### Comparación: escritura por lotes mediante el cliente estándar de DynamoDB
<a name="ddb-en-client-use-compare-cs4"></a>

```
    public static void batchWriteStandard(DynamoDbClient dynamoDbClient, String tableName) {

        Map<String, AttributeValue> catalogItem = Map.of(
                "authors", AttributeValue.builder().ss("a", "b").build(),
                "id", AttributeValue.builder().n("1").build(),
                "isbn", AttributeValue.builder().s("1-565-85698").build(),
                "title", AttributeValue.builder().s("Title 1").build(),
                "price", AttributeValue.builder().n("52.13").build());

        Map<String, AttributeValue> catalogItem2 = Map.of(
                "authors", AttributeValue.builder().ss("a", "b", "c").build(),
                "id", AttributeValue.builder().n("2").build(),
                "isbn", AttributeValue.builder().s("1-208-98073").build(),
                "title", AttributeValue.builder().s("Title 2").build(),
                "price", AttributeValue.builder().n("21.99").build());

        Map<String, AttributeValue> catalogItem3 = Map.of(
                "authors", AttributeValue.builder().ss("g", "k", "c").build(),
                "id", AttributeValue.builder().n("3").build(),
                "isbn", AttributeValue.builder().s("7-236-98618").build(),
                "title", AttributeValue.builder().s("Title 3").build(),
                "price", AttributeValue.builder().n("42.00").build());

        Set<WriteRequest> writeRequests = Set.of(
                WriteRequest.builder().putRequest(b -> b.item(catalogItem)).build(),
                WriteRequest.builder().putRequest(b -> b.item(catalogItem2)).build(),
                WriteRequest.builder().putRequest(b -> b.item(catalogItem3)).build());

        Map<String, Set<WriteRequest>> productCatalogItems = Map.of(
                "ProductCatalog", writeRequests);

        BatchWriteItemResponse response = dynamoDbClient.batchWriteItem(b -> b.requestItems(productCatalogItems));

        logger.info("Unprocessed items: " + response.unprocessedItems().size());
    }
```

### Comparación: escritura por lotes mediante el cliente mejorado de 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());
    }
```

# Trabajar con clases de datos inmutables
<a name="ddb-en-client-use-immut"></a>

La característica de asignación de la API de cliente mejorado de DynamoDB funciona con clases de datos inmutables. Una clase inmutable solo tiene getters y requiere una clase constructora que el SDK utiliza para crear instancias de la clase. En lugar de usar la anotación `@DynamoDbBean` como se muestra en la [clase Customer](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust), las clases inmutables usan la anotación `@DynamoDbImmutable`, que toma un parámetro que indica la clase de generador que se va a usar.

La siguiente clase es una versión inmutable 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); }
    }
}
```

Debe cumplir los siguientes requisitos al anotar una clase de datos con `@DynamoDbImmutable`.

1. Todo método que no sea una sustitución de `Object.class` y que no haya sido anotado con `@DynamoDbIgnore` debe ser un método de obtención de un atributo de la tabla de DynamoDB.

1. Cada getter debe tener un setter correspondiente que distinga entre mayúsculas y minúsculas en la clase de constructor.

1. Solo debe cumplirse una de las siguientes condiciones de construcción.
   + La clase de constructor debe tener un constructor público predeterminado.
   + La clase de datos debe tener un nombre de método estático público `builder()` que no tome parámetros y devuelva una instancia de clase constructor. Esta opción se muestra en la clase `Customer` inmutable.

1.  La clase del constructor debe tener un método público llamado `build()` que no acepte parámetros y devuelva una instancia de la clase inmutable. 

Para crear un `TableSchema` para su clase inmutable, utilice el método `fromImmutableClass()` en `TableSchema` como se muestra en el siguiente fragmento.

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

Del mismo modo que puede crear una tabla de DynamoDB a partir de una clase mutable, también puede crear una a partir de una clase inmutable con una *llamada única* a `createTable()` de `DynamoDbTable` como se muestra en el siguiente ejemplo de fragmento.

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

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

Las bibliotecas de terceros, como [Project Lombok](https://projectlombok.org/), ayudan a generar código reutilizable asociado a objetos inmutables. La API de cliente mejorado de DynamoDB funciona con estas bibliotecas siempre que las clases de datos sigan las convenciones detalladas en esta sección. 

En el siguiente ejemplo, se muestra la clase `CustomerImmutable` inmutable con anotaciones de Lombok. Observe cómo la característica `onMethod` de Lombok copia las anotaciones de DynamoDB basadas en atributos, como `@DynamoDbPartitionKey`, en el código generado.

```
@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 expresiones y condiciones
<a name="ddb-en-client-expressions"></a>

Las expresiones de la API de cliente mejorado de DynamoDB son representaciones Java de las [expresiones de DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html).

La API de cliente mejorado de DynamoDB utiliza tres tipos de expresiones:

[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)  
La clase `Expression` se usa al definir condiciones y 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)  
Este tipo de expresión representa las [condiciones clave para las](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions) operaciones 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)  
Esta clase le ayuda a escribir [expresiones de actualización](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html) de DynamoDB y actualmente se usa en el marco de extensiones al actualizar un elemento.

## Anatomía de una expresión
<a name="ddb-en-client-expressions-compoonents"></a>

Una expresión consta de lo siguiente:
+ Una expresión de cadena (obligatoria). La cadena contiene una expresión lógica de DynamoDB con nombres de marcadores de posición para los nombres y valores de los atributos.
+ Un mapa de valores de la expresión (normalmente obligatorio).
+ Un mapa de nombres de expresiones (opcional).

Utilice un generador para generar un objeto `Expression` que adopte la siguiente forma general.

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

Las `Expression` suelen requerir un mapa de valores de expresión. El mapa proporciona los valores de los marcadores de posición de la expresión de cadena. La clave del mapa consta del nombre del marcador de posición precedido de dos puntos (`:`) y el valor del mapa es una instancia de. [AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) La [AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/AttributeValues.html)clase tiene métodos prácticos para generar una `AttributeValue` instancia a partir de un literal. Como alternativa, puede usar el `AttributeValue.Builder` para generar una instancia de `AttributeValue`.

El siguiente fragmento muestra un mapa con dos entradas después de la línea de comentarios 2. La cadena pasada al método `expression()`, mostrada después de la línea de comentario 1, contiene los marcadores de posición que DynamoDB resuelve antes de realizar la operación. Este fragmento no contiene un mapa de nombres de expresiones, ya que el *precio* es un nombre de atributo permitido.

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

Si el nombre de un atributo de la tabla de DynamoDB es una palabra reservada, comienza por un número o contiene un espacio, se requiere un mapa de nombres de expresiones para el `Expression`.

Por ejemplo, si el nombre del atributo fuera `price` en lugar de `1price` en el ejemplo de código anterior, habría que modificar el ejemplo como se muestra en el siguiente ejemplo.

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

Un marcador de posición del nombre de una expresión comienza con el signo de almohadilla (`#`). Una entrada del mapa de nombres de expresiones utiliza el marcador de posición como clave y el nombre del atributo como valor. El mapa se agrega al generador de expresiones con el método `expressionNames()`. DynamoDB resuelve el nombre del atributo antes de efectuar la operación.

Los valores de expresión no son necesarios si se utiliza una función en la expresión de cadena. Un ejemplo de función de expresión es `attribute_exists(<attribute_name>)`.

En el ejemplo siguiente, se crea una `Expression` que utiliza una [función de DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions). La cadena de expresión de este ejemplo no utiliza marcadores de posición. Esta expresión podría usarse en una operación `putItem` para comprobar si ya existe un elemento en la base de datos con un valor de atributo `movie` igual al atributo `movie` del objeto de datos.

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

La Guía para desarrolladores de DynamoDB contiene información completa sobre las [expresiones de bajo nivel](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html) que se utilizan con DynamoDB.

## Expresiones de condición y condicionales
<a name="ddb-en-client-expressions-cond"></a>

Cuando se utilizan los métodos `putItem()`, `updateItem()` y `deleteItem()`, y también cuando se utilizan operaciones de transacción y lote, se usan objetos `[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)` para especificar las condiciones que DynamoDB debe cumplir para continuar con la operación. Estas expresiones se denominan expresiones de condición. Para ver un ejemplo, consulte la expresión de condición utilizada en el método `addDeleteItem()` (después de la línea de comentario 1) del [ejemplo de transacción](ddb-en-client-use-multiop-trans.md#ddb-en-client-use-multiop-trans-writeitems-opcondition) que se muestra en esta guía.

Cuando se trabaja con los métodos `query()`, una condición se expresa como [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). La clase `QueryConditional` tiene varios métodos prácticos estáticos que le ayudan a escribir los criterios que determinan qué elementos leer de DynamoDB.

Para ver ejemplos de `QueryConditionals`, consulte el primer ejemplo de código de la sección [Ejemplos del método `Query`](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-query-example) de esta guía.

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

Las expresiones de filtro se utilizan en las operaciones de análisis y consulta para filtrar los elementos que se devuelven. 

Una expresión de filtro se aplica después de haber leído todos los datos de la base de datos, por lo que el coste de lectura es el mismo que si no hubiera filtro. La *Guía para desarrolladores de Amazon DynamoDB* contiene más información sobre el uso de expresiones de filtro para las operaciones de [consulta](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression) y [análisis](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression).

En el ejemplo siguiente, se muestra una expresión de filtro agregada a una solicitud de análisis. El criterio restringe los artículos devueltos a artículos con un precio entre 8 y 80 euros, ambos incluidos.

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

## Expresiones de actualización
<a name="ddb-en-client-expressions-update"></a>

El método `updateItem()` de cliente mejorado de DynamoDB proporciona una forma estándar de actualizar elementos en DynamoDB. [Sin embargo, cuando necesite más funciones, [UpdateExpressions](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)proporcione una representación segura de la sintaxis de las expresiones de actualización de DynamoDB.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html) Por ejemplo, puede usar `UpdateExpressions` para aumentar los valores sin leer primero los elementos de DynamoDB o agregar miembros individuales a una lista. Las expresiones de actualización están disponibles actualmente en extensiones personalizadas para el método `updateItem()`.

Para ver un ejemplo en el que se utilizan expresiones de actualización, consulte el [ejemplo de extensión personalizada](ddb-en-client-extensions-custom.md) de esta guía.

Encontrará más información sobre las expresiones de actualización en la [Guía para desarrolladores de Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html).

# Trabajar con resultados paginados: análisis y consultas
<a name="ddb-en-client-use-multirecord"></a>

Los métodos `scan`, `query` y `batch` de la API de cliente mejorado de DynamoDB devuelven respuestas con una o varias *páginas*. Una página contiene uno o varios elementos. El código puede procesar la respuesta por página o procesar elementos individuales.

Una respuesta paginada devuelta por el `DynamoDbEnhancedClient` cliente síncrono devuelve un [PageIterable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PageIterable.html)objeto, mientras que una respuesta devuelta por el cliente `DynamoDbEnhancedAsyncClient` asíncrono devuelve un objeto. [PagePublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PagePublisher.html)

En esta sección se analiza el procesamiento de los resultados paginados y se proporcionan ejemplos en los que se utilizan el escaneo y la consulta. APIs

## Examinar una tabla
<a name="ddb-en-client-use-multirecord-scan"></a>

El método [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)) del SDK corresponde a la [operación de DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) del mismo nombre. La API de cliente mejorado de DynamoDB ofrece las mismas opciones, pero utiliza un modelo de objetos conocido y gestiona la paginación por usted.

En primer lugar, exploramos la `PageIterable` interfaz analizando el `scan` método de la clase de mapeo síncrono,. [DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)

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

El siguiente ejemplo muestra el método `scan` que usa una [expresión](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html) para filtrar los elementos que se devuelven. [ProductCatalog](ddb-en-client-use.md#ddb-en-client-use-compare-cs3)Es el objeto modelo que se mostró anteriormente.

La expresión de filtrado que se muestra después de la línea de comentario 2 limita los `ProductCatalog` artículos devueltos a aquellos con un precio comprendido entre 8,00 y 80,00€, ambos inclusive.

En este ejemplo también se excluyen `isbn` los valores mediante el `attributesToProject` método que se muestra después de la línea de comentario 1.

Después de la línea de comentario 3, el objeto `PageIterable`, `pagedResults`, es devuelto por el método `scan`. El método `stream` de `PageIterable` devuelve un 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 puede utilizar para procesar las páginas. En este ejemplo, se cuenta y se registra el número de páginas.

Empezando por la línea de comentarios 4, el ejemplo muestra dos variantes del acceso a los elementos de `ProductCatalog`. La versión posterior a la línea de comentarios 4a recorre cada página y ordena y registra los elementos de cada página. La versión siguiente a la línea de comentarios 4b omite la iteración de la página y accede a los elementos directamente.

La interfaz `PageIterable` ofrece varias formas de procesar los resultados debido a sus dos interfaces principales, [https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) y [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` trae los métodos `forEach`, `iterator` y `spliterator`, y `SdkIterable` el 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())
                );
    }
```

### Utilizar la API asíncrona
<a name="ddb-en-client-use-multirecord-scan-async"></a>

El método `scan` asíncrono devuelve los resultados como un objeto `PagePublisher`. La interfaz de `PagePublisher` tiene dos métodos `subscribe` que puede utilizar para procesar las páginas de respuesta. Un método `subscribe` proviene de la interfaz principal `org.reactivestreams.Publisher`. Para procesar páginas con esta primera opción, pase una instancia `[Subscriber](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Subscriber.html)` al método `subscribe`. En el ejemplo siguiente se muestra el uso del método `subscribe`.

El segundo `subscribe` método proviene de la interfaz. [SdkPublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html) Esta versión de `subscribe` acepta un [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) en lugar de un `Subscriber`. Esta variación del método `subscribe` se muestra en el segundo ejemplo siguiente.

El ejemplo siguiente muestra la versión asíncrona del método `scan` que utiliza la misma expresión de filtro que se muestra en el ejemplo anterior. 

Tras la línea de comentario 3, `DynamoDbAsyncTable.scan` devuelve un objeto `PagePublisher`. En la siguiente línea, el código crea una instancia de la interfaz de `org.reactivestreams.Subscriber`, `ProductCatalogSubscriber`, que se suscribe a la `PagePublisher` después de la línea de comentarios 4.

El objeto `Subscriber` recopila los elementos `ProductCatalog` de cada página del método `onNext` después de la línea de comentarios 8 del ejemplo de clase `ProductCatalogSubscriber`. Los elementos se almacenan en la variable `List` privada y se accede a ellos en el código de llamada con el método `ProductCatalogSubscriber.getSubscribedItems()`. Esto se invoca después de la línea de comentarios 5.

Una vez recuperada la lista, el código ordena todos los artículos `ProductCatalog` por precio y registra cada uno de ellos.

El comando [CountDownLatch](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html)in the `ProductCatalogSubscriber` class bloquea el hilo de llamada hasta que todos los elementos se hayan agregado a la lista antes de continuar después de la línea de comentarios 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;
        }
    }
```

En el siguiente ejemplo de fragmento, se utiliza la versión del método `PagePublisher.subscribe` que acepta una `Consumer` después de la línea de comentario 6. El parámetro lambda de Java consume páginas, que procesan aún más cada elemento. En este ejemplo, se procesa cada página y los elementos de cada página se ordenan y, a continuación, se registran.

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

El método `items` de `PagePublisher` separa las instancias del modelo para que el código pueda procesar los elementos directamente. Este método se muestra en el fragmento de código siguiente.

```
        // 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 una tabla
<a name="ddb-en-client-use-multirecord-query"></a>

Puede utilizar DynamoDB Enhanced Client para consultar la tabla y recuperar varios elementos que coincidan con criterios específicos. El 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)) busca los elementos en función de los valores clave principales mediante la anotación `@DynamoDbPartitionKey` y, opcionalmente, la `@DynamoDbSortKey` definidas en la clase de datos.

El método `query()` requiere un valor de clave de partición y, de forma opcional, acepta condiciones de clave de clasificación para afinar aún más los resultados. Al igual que la API `scan`, las consultas devuelven una `PageIterable` para llamadas sincrónicas y una `PagePublisher` para asincrónicas.

### Ejemplos del método `Query`
<a name="ddb-en-client-use-multirecord-query-example"></a>

El ejemplo de código del método `query()` siguiente utiliza la clase `MovieActor`. La clase de datos define una clave primaria compuesta que se compone del atributo **`movie`** como clave de partición y el atributo **`actor`** como clave de clasificación. 

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

Los ejemplos de código que aparecen a continuación se refieren a los siguientes elementos.

#### Elementos de la tabla `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'}
```

El siguiente código define dos instancias `QueryConditional`: `keyEqual` (después de la línea de comentario 1) y `sortGreaterThanOrEqualTo` (después de la línea de comentario 1a).

#### Consulta de elementos por clave de partición
<a name="keyEqual-query-conditional-example"></a>

La instancia `keyEqual` asocia los elementos con un valor de clave de partición de **`movie01`**. 

Este ejemplo también define una expresión de filtro después de la línea de comentario 2 que filtra cualquier elemento que no tenga un valor de **`actingschoolname`**.

La `QueryEnhancedRequest` combina la condición de clave y la expresión de filtro de la 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 — Salida utilizando el condicional de consulta `keyEqual`**  
Se genera la siguiente salida de la ejecución del método. El resultado muestra los elementos con un valor `movieName` de **movie01** y no muestra ningún elemento con `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'}
```

#### Consulta de elementos por clave de partición y clave de clasificación
<a name="sort-type-query-conditional-example"></a>

La `QueryConditional` `sortGreaterThanOrEqualTo` afina la coincidencia de clave de partición (**movie01**) añadiendo una condición de clave de clasificación para valores mayores o iguales que **actor2**.

Los [métodos de `QueryConditional`](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html) que comienzan por `sort` requieren un valor de clave de partición para asociar y afinar aún más la consulta mediante una comparación basada en el valor de la clave de clasificación. `Sort` en el nombre del método no significa que los resultados estén clasificados, sino que se utilizará un valor de clave de clasificación para comparación.

En el siguiente fragmento cambiamos la solicitud de consulta que mostrada anteriormente después de la línea de comentario 3. Este fragmento reemplaza el condicional de consulta «keyEqual» por el condicional de consulta «» que se definió sortGreaterThan OrEqualTo después de la línea de comentario 1a. El código siguiente también elimina la expresión del filtro.

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

**Example — Salida utilizando el condicional de consulta `sortGreaterThanOrEqualTo`**  
La siguiente salida muestra los resultados de la consulta. La consulta devuelve los elementos que tienen un valor `movieName` igual a **movie01** y solo los elementos que tienen un valor `actorName` mayor o igual a **actor2**. Dado que quitamos el filtro, la consulta devuelve elementos que no tienen ningún valor para el 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 operaciones por lotes
<a name="ddb-en-client-use-multiop-batch"></a>

La API de cliente mejorado de DynamoDB ofrece dos métodos por lotes, [`batchGetItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) y [`batchWriteItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchWriteItem(java.util.function.Consumer)).

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

Con el 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)), puede recuperar hasta 100 elementos individuales de varias tablas en una solicitud general. En el siguiente ejemplo, se utilizan las clases de datos [`Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) y [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) mostradas anteriormente.

En el ejemplo, después de las líneas 1 y 2, se crean objetos `[ReadBatch](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ReadBatch.html)` que se añaden posteriormente como parámetros al método `batchGetItem()` después de la línea de comentarios 3. 

El código que sigue a la línea de comentarios 1 crea el lote para leerlo de la tabla `Customer`. El código que sigue a la línea de comentario 1a muestra el uso de un compilador de `[GetItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/GetItemEnhancedRequest.Builder.html)` que toma un valor de clave principal y un valor de clave de clasificación para especificar el elemento que se va a leer. Si la clase de datos tiene una clave compuesta, debe proporcionar tanto el valor de la clave de partición como el valor de la clave de clasificación. 

A diferencia de especificar valores clave para solicitar un elemento, puede usar una clase de datos para solicitar un elemento, como se muestra después de la línea de comentario 1b. El SDK extrae automáticamente los valores clave de manera interna antes de enviar la solicitud.

Cuando especifique el elemento utilizando el enfoque basado en claves, como se muestra en las dos instrucciones posteriores a 2a, también puede especificar que DynamoDB realice una [lectura altamente coherente](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html). Cuando se utilice el método `consistentRead()`, se debe utilizar en todos los elementos solicitados de la misma tabla.

Para recuperar los elementos encontrados por DynamoDB, utilice el 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))` que se muestra después de la línea de comentarios 4. Llame al método de cada tabla que se haya leído en la solicitud. `resultsForTable()` devuelve una lista de los elementos encontrados que puede procesar mediante cualquier método `java.util.List`. En este ejemplo se registra cada elemento.

Para descubrir elementos que DynamoDB no procesó, utilice el enfoque que aparece después de la línea de comentarios 5. La clase `BatchGetResultPage` tiene el 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 permite acceder a todas las claves que no se hayan procesado. La [referencia de la BatchGetItem API contiene](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) más información sobre situaciones que dan como resultado elementos sin procesar.

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

Suponga que los siguientes elementos están en las dos tablas antes de ejecutar el código de ejemplo.

### Elementos de la tabla
<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'}
```

El siguiente resultado muestra los elementos devueltos y registrados después de la línea de comentarios 4.

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

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

El método `batchWriteItem()` pone o borra varios elementos en una o varias tablas. Puede especificar hasta 25 operaciones individuales de colocación o borrado en la solicitud. En el siguiente ejemplo, se utilizan las clases de modelos [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) y [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) mostradas anteriormente.

Los objetos de `WriteBatch` se crean después de las líneas de comentario 1 y 2. En la tabla `ProductCatalog`, el código coloca un elemento y elimina otro. En la tabla `MovieActor` situada después de la línea de comentarios 2, el código coloca dos elementos y elimina uno.

El método `batchWriteItem` se llama después de la línea de comentarios 3. El parámetro `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteItemEnhancedRequest.Builder.html)` proporciona las solicitudes de lote para cada tabla.

El objeto devuelto `[BatchWriteResult](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteResult.html)` proporciona métodos independientes para cada operación a fin de ver las solicitudes no procesadas. El código que sigue a la línea de comentarios 4a proporciona las claves para las solicitudes de eliminación sin procesar y el código que sigue a la línea de comentarios 4b proporciona los elementos de venta sin procesar.

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

Los siguientes métodos auxiliares proporcionan los objetos modelo para las operaciones de colocación y eliminación.

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

Suponga que las tablas contienen los siguientes elementos antes de ejecutar el código de ejemplo.

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

Una vez finalizado el código de ejemplo, las tablas contienen los siguientes elementos.

```
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 en la tabla `MovieActor` que el elemento de la película `Blue Jasmine` se ha sustituido por el elemento utilizado en la solicitud de venta adquirida mediante el método auxiliar `getMovieActorBlanchettPartial()`. Si no se ha proporcionado el valor de un atributo de un objeto de datos, se elimina el valor de la base de datos. Esta es la razón por la que el `actingSchoolName` resultante es nulo para el elemento de la película `Blue Jasmine`.

**nota**  
Aunque la documentación de la API sugiere que pueden utilizarse expresiones de condición y que las métricas de capacidad consumida y de recogida pueden devolverse con solicitudes individuales de [put](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PutItemEnhancedRequest.html) y [delete](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/DeleteItemEnhancedRequest.html), esto no es así en un escenario de escritura por lotes. Para mejorar el rendimiento de las operaciones por lotes, se ignoran estas opciones individuales.

# Realizar operaciones de transacciones
<a name="ddb-en-client-use-multiop-trans"></a>

La API de cliente mejorado de DynamoDB proporciona los `transactGetItems()` y los métodos `transactWriteItems()`. Los métodos de transacción del SDK para Java proporcionan atomicidad, consistencia, aislamiento y durabilidad (ACID) en las tablas de DynamoDB, ayudándole a mantener la corrección de los datos en sus aplicaciones.

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

El método `[transactGetItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactGetItems(java.util.function.Consumer))` admite hasta 100 solicitudes individuales de artículos. Todos los artículos se leen en una sola transacción atómica. La *Guía para desarrolladores de Amazon DynamoDB* contiene información sobre las [condiciones que hacen que un método `transactGetItems()` falle](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txgetitems), y también sobre el nivel de aislamiento utilizado cuando se llama a `[transactGetItem()](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-isolation)`.

Tras la línea de comentarios 1 del siguiente ejemplo, el código llama al método `transactGetItems()` con un parámetro `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html)`. El generador `[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))` se invoca tres veces con un objeto de datos que contiene los valores clave que el SDK utilizará para generar la solicitud final.

La solicitud devuelve una lista de objetos `[Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html)` después de la línea de comentarios 2. La lista de documentos que se devuelve contiene instancias de [documentos](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html) no nulas de los datos de los artículos en el mismo orden solicitado. El 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))` convierte un objeto sin tipo en un objeto `Document` Java con tipo si se devolvieron los datos del elemento; de lo contrario, el método devuelve un valor 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());
        }
    }
```

Las tablas de DynamoDB contienen los siguientes elementos antes de que se ejecute el ejemplo 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'}
```

Se registra la siguiente salida. Si se solicita un elemento pero no se encuentra, no se devuelve, como ocurre con la solicitud de la película nombrada `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'}
```

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

El `[transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))` acepta hasta 100 acciones para colocar, actualizar o eliminar en una única transacción atómica a través de varias tablas. La *Guía para desarrolladores de Amazon DynamoDB* contiene detalles sobre las restricciones y las condiciones de fallo del [funcionamiento del servicio DynamoDB subyacente](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems).

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

En el ejemplo siguiente, se solicitan cuatro operaciones para dos tablas. Las clases de modelos correspondientes [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) y [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) se mostraron anteriormente.

Cada una de las tres operaciones posibles (poner, actualizar y eliminar) utiliza un parámetro de solicitud específico para especificar los detalles. 

El código bajo la línea de comentarios 1 muestra la variación simple del método `addPutItem()`. El método acepta un objeto `[MappedTableResource](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.html)` y la instancia del objeto de datos para colocarlo. La declaración que sigue a la línea de comentarios 2 muestra la variación que acepta una instancia `[TransactPutItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactPutItemEnhancedRequest.html)`. Esta variante permite añadir más opciones a la solicitud, como una expresión de condición. En un [ejemplo](#ddb-en-client-use-multiop-trans-writeitems-opcondition) posterior, se muestra una expresión de condición para una operación individual.

Se solicita una operación de actualización después de la línea de comentario 3. `[TransactUpdateItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactUpdateItemEnhancedRequest.Builder.html)` tiene un método `ignoreNulls()` que permite configurar lo que hace el SDK con los valores `null` del objeto modelo. Si el método `ignoreNulls()` devuelve el valor true, el SDK no elimina los valores de los atributos de la tabla para los atributos del objeto de datos que son `null`. Si el método `ignoreNulls()` devuelve false, el SDK solicita al servicio DynamoDB que elimine los atributos del elemento de la tabla. El valor predeterminado de `ignoreNulls` es false.

La instrucción tras la línea de comentario 4 muestra la variación de una petición de borrado que toma un objeto de datos. El cliente mejorado extrae los valores clave antes de enviar la solicitud 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())
        );
    }
```

Los siguientes métodos auxiliares proporcionan los objetos de datos para los 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;
    }
```

Las tablas de DynamoDB contienen los siguientes elementos antes de que se ejecute el ejemplo 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'}
```

Los siguientes elementos aparecen en las tablas después de que el código termine de ejecutarse.

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

Se ha eliminado el elemento de la línea 2 y las líneas 3 y 5 muestran los elementos que se colocaron. La línea 4 muestra la actualización de la línea 1. El valor `price` es el único que ha cambiado en el artículo. Si `ignoreNulls()` hubiera devuelto false, la línea 4 tendría el siguiente aspecto.

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

### Ejemplo de comprobación de estado
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond"></a>

En el siguiente ejemplo, se muestra el uso de una comprobación de estado. Una comprobación de estado se utiliza para comprobar la existencia de un elemento o para comprobar el estado de atributos concretos de un elemento en la base de datos. El artículo registrado en la verificación de estado no se puede utilizar en otra operación de la transacción.

**nota**  
No se pueden dirigir varias operaciones de la misma transacción al mismo elemento. Por ejemplo, no puede llevar a cabo una comprobación de condiciones y además intentar actualizar el mismo artículo en la misma transacción.

El ejemplo muestra una operación de cada tipo en una solicitud transaccional de escritura de artículos. Después de la línea de comentario 2, el método `addConditionCheck()` suministra la condición que falla la transacción si el parámetro `conditionExpression` se evalúa como `false`. La expresión de condición que devuelve el método mostrado en el bloque Métodos auxiliares comprueba si el año de concesión de la película `Sophie's Choice` no es igual a `1982`. Si es así, la expresión se evalúa como `false` y la transacción no se realiza correctamente.

Esta guía analiza en profundidad [las expresiones](ddb-en-client-expressions.md) en otro tema.

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

En el ejemplo de código anterior se utilizan los siguientes métodos auxiliares.

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

Las tablas de DynamoDB contienen los siguientes elementos antes de que se ejecute el ejemplo 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'}
```

Los siguientes elementos aparecen en las tablas después de que el código termine de ejecutarse.

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

Los elementos permanecen sin cambios en las tablas porque se produjo un error en la transacción. El valor `actingYear` de la película `Sophie's Choice` es `1982`, como muestra la línea 2 de los elementos de la tabla antes de llamar al método `transactWriteItem()`.

Para capturar la información de cancelación de la transacción, incluya la llamada al método `transactWriteItems()` en un bloque `try` y `catch` el [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). Tras la línea de comentario 4 del ejemplo, el código registra cada objeto `[CancellationReason](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CancellationReason.html)`. Dado que el código que sigue a la línea de comentario 3 del ejemplo especifica que deben devolverse los valores del elemento que provocó el fallo de la transacción, el registro muestra los valores brutos de la base de datos para el elemento de la película `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.)
```

### Ejemplo de condición de operación única
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition"></a>

El siguiente ejemplo muestra el uso de una condición en una sola operación de una solicitud de transacción. La operación de eliminación después de la línea de comentarios 1 contiene una condición que compara el valor del elemento objetivo de la operación con la base de datos. En este ejemplo, la expresión de condición creada con el método auxiliar después de la línea de comentario 2 especifica que el elemento debe eliminarse de la base de datos si el año de actuación de la película no es igual a 2013.

Las [expresiones](ddb-en-client-expressions.md) se describen más adelante en esta guía.

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

En el ejemplo de código anterior se utilizan los siguientes métodos auxiliares.

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

Las tablas de DynamoDB contienen los siguientes elementos antes de que se ejecute el ejemplo 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'}
```

Los siguientes elementos aparecen en las tablas después de que el código termine de ejecutarse.

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

Los elementos permanecen sin cambios en las tablas porque se produjo un error en la transacción. El valor `actingYear` para la película `Blue Jasmine` es `2013` como se muestra en la línea 2 de la lista de elementos antes de que se ejecute el ejemplo de código.

Las líneas siguientes se registran en la consola.

```
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 secundarios
<a name="ddb-en-client-use-secindex"></a>

Los índices secundarios mejoran el acceso a los datos al definir claves alternativas que se utilizan en las operaciones de consulta y análisis. Índice secundario global (GSI): índice con una clave de partición y una clave de clasificación que pueden ser diferentes de las de la tabla base. Por el contrario, los índices secundarios locales (LSI) utilizan la clave de partición del índice principal.

## Anote la clase de datos con anotaciones de índice secundarias
<a name="ddb-en-client-use-secindex-annomodel"></a>

Los atributos que participan en índices secundarios requieren la anotación `@DynamoDbSecondaryPartitionKey` o `@DynamoDbSecondarySortKey`.

La siguiente clase muestra las anotaciones de dos índices. El GSI denominado *SubjectLastPostedDateIndex*usa el `Subject` atributo para la clave de partición y el atributo `LastPostedDateTime` para la clave de clasificación. El LSI denominado *ForumLastPostedDateIndex*utiliza el `ForumName` como clave de partición y `LastPostedDateTime` como clave de clasificación.

Tenga en cuenta que el atributo `Subject` cumple una doble función. Es la clave de clasificación de la clave principal y la clave de partición del GSI denominado. *SubjectLastPostedDateIndex*

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

La clase `MessageThread` es adecuada para utilizarla como clase de datos en la [tabla Thread de ejemplo](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AppendixSampleTables.html) de la *Guía para desarrolladores de Amazon DynamoDB*.

#### Importaciones
<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 +
                '}';
    }
}
```

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

A partir de la versión 2.20.86 del SDK para Java, el método `createTable()` genera automáticamente índices secundarios a partir de las anotaciones de las clases de datos. De forma predeterminada, todos los atributos de la tabla base se copian en un índice y los valores de rendimiento aprovisionados son 20 unidades de capacidad de lectura y 20 unidades de capacidad de escritura.

Sin embargo, si utiliza una versión del SDK anterior a la 2.20.86, debe crear el índice junto con la tabla, como se muestra en el siguiente ejemplo. En este ejemplo, se crean los dos índices de la tabla `Thread`. El parámetro [constructor](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html) tiene métodos para configurar ambos tipos de índices, como se muestra después de las líneas de comentario 1 y 2. Utilice el método del generador de índices `indexName()` para asociar los nombres de índice especificados en las anotaciones de las clases de datos con el tipo de índice deseado.

Este código configura todos los atributos de la tabla para que terminen en ambos índices después de las líneas de comentario 3 y 4. Encontrará más información sobre las [proyecciones de atributos](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.Projections) en la *Guía para desarrolladores de 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 utilizando un índice
<a name="ddb-en-client-use-secindex-query"></a>

El siguiente ejemplo consulta el índice secundario global *ForumLastPostedDateIndex*.

Tras la línea de comentario 2, se crea un [QueryConditional](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)objeto que es obligatorio al llamar al método [DynamoDbIndex.query ()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbIndex.html#query(java.util.function.Consumer)). 

Para obtener una referencia al índice que desea consultar después de la línea de comentario 3, debe pasar al nombre del índice. Tras la línea de comentario 4, se llama al método `query()` del índice que pasa al objeto `QueryConditional`. 

También puede configurar la consulta para que devuelva tres valores de atributo, como se muestra tras la línea de comentario 5. Si no se llama `attributesToProject()`, la consulta devuelve todos los valores de los atributos. Los nombres de los atributos especificados comienzan con letras minúsculas. Estos nombres coinciden con los utilizados en la tabla, no necesariamente con los de la clase de datos.

Siguiendo la línea de comentario 6, se itera a través de los resultados y se registra cada elemento devuelto por la consulta y también se almacena en la lista para devolvérsela a la persona que llama.

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

En la base de datos existen los siguientes elementos antes de que se ejecute la 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}
```

Las instrucciones de registro de las líneas 1 y 6 dan como resultado la siguiente salida de consola.

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

La consulta devolvió elementos con un valor `forumName` de *Forum02* y un valor de `lastPostedDateTime` superior o igual a *2023.03.31*. Los resultados muestran valores `message` con una cadena vacía, aunque los atributos `message` tienen valores en el índice. Esto se debe a que el atributo del mensaje no se proyectó mediante código después de la línea de comentario 5. 

# Utilizar características de asignación avanzadas
<a name="ddb-en-client-adv-features"></a>

Obtenga información sobre las características avanzadas del esquema de tablas en la API del cliente mejorado de DynamoDB.

## Comprender los tipos de esquema de tabla
<a name="ddb-en-client-adv-features-schm-overview"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` es la interfaz para la funcionalidad de asignación de la API del cliente mejorado de DynamoDB. Puede mapear un objeto de datos hacia y desde un mapa de [AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html). Un objeto `TableSchema` necesita conocer la estructura de la tabla que está asignando. Esta información de estructura se almacena en un objeto [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html).

La API del cliente mejorado tiene varias implementaciones de `TableSchema`, indicadas a continuación. 

### Esquema de tabla generado a partir de clases anotadas
<a name="ddb-en-client-adv-features-schema-mapped"></a>

Es una operación algo costosa construir un `TableSchema` a partir de clases anotadas, por lo que recomendamos hacerlo una vez, al inicio de la aplicación.

 [ BeanTableSchema ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.html)   
Esta implementación se construye basándose en atributos y anotaciones de una clase de bean. En la sección [Introducción](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean) se muestra un ejemplo de este enfoque.  
Si un `BeanTableSchema` no se comporta como espera, active el registro de depuración para `software.amazon.awssdk.enhanced.dynamodb.beans`.

[ImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/ImmutableTableSchema.html)  
Esta implementación se crea a partir de una clase de datos inmutable. Esto se describe en la sección [Trabajar con clases de datos inmutables](ddb-en-client-use-immut.md) anterior.

### Esquema de tabla generado con un generador
<a name="ddb-en-client-adv-features-schema-static"></a>

Los siguientes `TableSchema` se crean a partir de código mediante un generador. Este enfoque es menos costoso que el enfoque que usa clases de datos anotadas. El enfoque de creación evita el uso de anotaciones y no requiere estándares de JavaBean nomenclatura.

[StaticTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.html)  
Esta implementación está creada para clases de datos mutables. En la sección de introducción de esta guía se muestra cómo [crear un `StaticTableSchema` mediante un generador](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder).

[StaticImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)  
De forma similar a como se crea un `StaticTableSchema`, se genera una implementación de este tipo de `TableSchema` utilizando un [generador](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html) para su uso con clases de datos inmutables.

### Esquema de tabla para datos sin un esquema fijo
<a name="ddb-en-client-adv-features-schema-document"></a>

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)  
A diferencia de otras implementaciones de `TableSchema`, no define los atributos de una instancia `DocumentTableSchema`. Por lo general, solo especifica las claves principales y los proveedores de convertidores de atributos. Una instancia `EnhancedDocument` proporciona los atributos que se crean a partir de elementos individuales o de una cadena JSON.

# Incluir o excluir explícitamente atributos
<a name="ddb-en-client-adv-features-inex-attr"></a>

La API del cliente mejorado de DynamoDB ofrece anotaciones para impedir que los atributos de las clases de datos se conviertan en atributos de una tabla. Con la API, también puede usar un nombre de atributo diferente del nombre de atributo de la clase de datos.

## Excluir atributos
<a name="ddb-en-client-adv-features-inex-attr-ex"></a>

Para ignorar los atributos que no se deben asignar a una tabla de DynamoDB, marque el atributo con la anotación `@DynamoDbIgnore`.

```
private String internalKey;

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

## Incluir atributos
<a name="ddb-en-client-adv-features-inex-attr-in"></a>

Para cambiar el nombre de un atributo utilizado en la tabla de DynamoDB, márquelo con la anotación `@DynamoDbAttribute` y escriba un nombre diferente.

```
private String internalKey;

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

# Conversión de atributos de control
<a name="ddb-en-client-adv-features-conversion"></a>

De forma predeterminada, un esquema de tabla proporciona convertidores para muchos tipos comunes de Java mediante una implementación predeterminada de la interfaz de `[AttributeConverterProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.html)`. Puede cambiar el comportamiento predeterminado general con una implementación de `AttributeConverterProvider` personalizada. También puede cambiar el conversor de un solo atributo.

Para obtener una lista de los convertidores disponibles, consulte el documento de la [AttributeConverter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html)interfaz Java.

## Proporcionar proveedores de convertidores de atributos personalizados
<a name="ddb-en-client-adv-features-conversion-prov"></a>

Puede proporcionar una `AttributeConverterProvider` única o una cadena de `AttributeConverterProvider` ordenadas a través de la anotación `@DynamoDbBean` `(converterProviders = {…})`. Cualquier `AttributeConverterProvider` personalizada debe extender la interfaz `AttributeConverterProvider`.

Tenga en cuenta que si suministra su propia cadena de proveedores de convertidores de atributos, anulará el proveedor de convertidores predeterminado `DefaultAttributeConverterProvider`. Si desea utilizar la funcionalidad del `DefaultAttributeConverterProvider`, debe incluirlo en la cadena. 

También es posible anotar el bean con una matriz vacía `{}`. Esto deshabilita el uso de cualquier proveedor de conversión de atributos, incluido el predeterminado. En este caso, todos los atributos que se van a asignar deben tener su propio convertidor de atributos.

El fragmento siguiente muestra un único proveedor de convertidores.

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

}
```

El siguiente fragmento muestra el uso de una cadena de proveedores de convertidores. Como el SDK predeterminado es el último que se proporciona, tiene la prioridad más baja.

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

}
```

Los creadores de esquemas de tablas estáticas tienen un método `attributeConverterProviders()` que funciona de la misma manera. Esto se muestra en el siguiente fragmento.

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

## Sustituir la asignación de un único atributo
<a name="ddb-en-client-adv-features-conversion-single"></a>

Para sustituir la forma en que se asigna un único atributo, introduzca un `AttributeConverter` para el atributo. Esto anula los convertidores proporcionados por `AttributeConverterProviders` en el esquema de la tabla. Esto añade un conversor personalizado solo para ese atributo. Otros atributos, incluso los del mismo tipo, no utilizarán ese convertidor salvo que se especifique explícitamente para esos otros atributos.

La anotación `@DynamoDbConvertedBy` se usa para especificar la clase `AttributeConverter` personalizada, como se muestra en el siguiente fragmento.

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

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

Los generadores de esquemas estáticos tienen un método `attributeConverter()` de creación de atributos equivalente. Este método toma una instancia de un `AttributeConverter`, como se muestra a continuación.

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

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

En este ejemplo, se muestra una implementación de `AttributeConverterProvider` que proporciona un conversor de atributos para objetos [https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html). 

La siguiente clase `SimpleUser` contiene un nombre de atributo `lastUsedCookie` que es una instancia de `HttpCookie`.

El parámetro de las anotaciones de `@DynamoDbBean` muestra las dos clases de `AttributeConverterProvider` que proporcionan convertidores.

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

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

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

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

        public HttpCookie getLastUsedCookie() {
            return lastUsedCookie;
        }

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

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

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

------

El `CookieConverterProvider` en el ejemplo siguiente proporciona una instancia de un `HttpCookeConverter`.

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

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

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

### Conversión de códigos
<a name="ddb-en-client-adv-features-conversion-example-code"></a>

En el método `transformFrom()` de la clase `HttpCookieConverter` siguiente, el código recibe una instancia `HttpCookie` y la transforma en un mapa de DynamoDB que se almacena como un atributo.

El método `transformTo()` recibe un parámetro de mapa de DynamoDB y, a continuación, invoca el constructor `HttpCookie` que requiere un nombre y un valor.

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

        @Override
        public AttributeValue transformFrom(HttpCookie httpCookie) {

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

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

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

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

# Cambiar el comportamiento de actualización de los atributos
<a name="ddb-en-client-adv-features-upd-behavior"></a>

Puede personalizar el comportamiento de actualización de los atributos individuales al realizar una operación de *actualización*. [Algunos ejemplos de operaciones de actualización en la API de cliente mejorada de DynamoDB [son updateItem](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#updateItem(T)) () y (). transactWriteItems](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))

Por ejemplo, imagine que desea almacenar en su registro una marca de tiempo de tipo *creado en* el registro. Sin embargo, desea que su valor se escriba solo si no existe ningún valor para el atributo en la base de datos. En este caso, utiliza el comportamiento de actualización de `[WRITE\$1IF\$1NOT\$1EXISTS](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html#WRITE_IF_NOT_EXISTS)`.

En el siguiente ejemplo, se muestra la anotación que añade el comportamiento al `createdOn` atributo.

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

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

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

Puede declarar el mismo comportamiento de actualización al crear un esquema de tabla estática, como se muestra en el siguiente ejemplo después de la línea de comentario 1.

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

# Aplanar atributos de otras clases
<a name="ddb-en-client-adv-features-flatmap"></a>

Si los atributos de su tabla están repartidos en varias clases Java diferentes, ya sea por herencia o composición, la API del cliente mejorado de DynamoDB proporciona soporte para aplanar los atributos en una sola clase.

## Utilizar la herencia
<a name="ddb-en-client-adv-features-flatmap-inheritance"></a>

Si sus clases utilizan herencias, utilice los siguientes enfoques para aplanar la jerarquía.

### Utilizar objetos bean anotados
<a name="ddb-en-client-adv-features-flatmap-inheritance-anno"></a>

Para el método de anotación, ambas clases deben llevar la anotación `@DynamoDbBean` y una de ellas llevar una o más anotaciones de clave principal.

A continuación, se muestran ejemplos de clases de datos que tienen una relación de herencia.

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

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

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

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

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

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

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

La [opción `onMethod` de Lombok](https://projectlombok.org/features/experimental/onX) copia anotaciones de DynamoDB basadas en atributos, como `@DynamoDbPartitionKey`, en el código generado.

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

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

------

### Usar esquemas estáticos
<a name="ddb-en-client-adv-features-flatmap-inheritance-static"></a>

Para el enfoque de esquema estático, utilice el método `extend()` del generador para contraer los atributos de la clase principal en la clase secundaria. Esto se muestra después de la línea de comentario 1 en el siguiente ejemplo.

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

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

El ejemplo de esquema estático anterior utiliza las siguientes clases de datos. Dado que la asignación se define cuando se construye el esquema estático de la tabla, las clases de datos no requieren anotaciones.

#### Clases de datos
<a name="gunk"></a>

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

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

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


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

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

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

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

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

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

------

## Utilizar composiciones
<a name="ddb-en-client-adv-features-flatmap-comp"></a>

Si sus clases utilizan composiciones, siga los siguientes planteamientos para aplanar la jerarquía.

### Utilizar objetos bean anotados
<a name="ddb-en-client-adv-features-flatmap-comp-anno"></a>

La anotación `@DynamoDbFlatten` aplana la clase contenida.

Los siguientes ejemplos de clases de datos utilizan la anotación `@DynamoDbFlatten` para añadir de forma eficaz todos los atributos de la clase `GenericRecord` contenida a la clase `Customer`.

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

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

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

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

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

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

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

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

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

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

------

Puede utilizar la anotación aplanar para aplanar tantas clases elegibles diferentes como necesite. Existen las siguientes limitaciones:
+ Después de ser aplanados, todos los nombres de atributos deben ser únicos.
+ Nunca debe haber más de una clave de partición, clave de clasificación o nombre de tabla.

### Usar esquemas estáticos
<a name="ddb-en-client-adv-features-flatmap-comp-static"></a>

Cuando construya un esquema de tabla estático, utilice el método `flatten()` del constructor. También debe suministrar los métodos getter y setter que identifican la clase contenida.

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

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

El ejemplo de esquema estático anterior utiliza las siguientes clases de datos.

#### Clases de datos
<a name="ddb-en-client-adv-features-flatmap-comp-static-supporting"></a>

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

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

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

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

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

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

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

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

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

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

------

Puede utilizar el patrón constructor para aplanar tantas clases elegibles diferentes como necesite.

## Implicaciones para otro código
<a name="ddb-en-client-adv-features-flatmap-compare"></a>

Cuando se utiliza el atributo `@DynamoDbFlatten` (o el método constructor `flatten()`), el elemento en DynamoDB contiene un atributo para cada atributo del objeto compuesto. También incluye los atributos del objeto que lo compone. 

Por el contrario, si anota una clase de datos con una clase compuesta y no usa `@DynamoDbFlatten`, el elemento se guarda con el objeto compuesto como un atributo único.

Por ejemplo, compare la clase `Customer` que se muestra en el [ejemplo de aplanamiento con composición](#ddb-en-client-adv-features-flatmap-comp-anno) con y sin aplanamiento del atributo `record`. Puede visualizar la diferencia con JSON según muestra la siguiente tabla.


****  

| Con aplanamiento | Con aplanamiento | 
| --- | --- | 
| 3 atributos | 2 atributos | 
|  <pre>{<br />  "id": "1",<br />  "createdDate": "today",<br />  "name": "my name"<br />}</pre>  |  <pre>{<br />  "id": "1",<br />  "record": {<br />      "createdDate": "today",<br />      "name": "my name"<br />  }<br />}</pre>  | 

La diferencia es importante si hay otro código que accede a la tabla de DynamoDB y espera encontrar determinados atributos.

# Uso de atributos que son beans, mapas, listas y conjuntos
<a name="ddb-en-client-adv-features-nested"></a>

Una definición de bean, como la clase `Person` que se muestra a continuación, podría establecer propiedades (o atributos) que hagan referencia a tipos con atributos adicionales. Por ejemplo, en la clase `Person`, `mainAddress` es una propiedad que hace referencia a un bean `Address` que define atributos de valor adicionales. `addresses` hace referencia a un mapa de Java, cuyos elementos hacen referencia a beans `Address`. Estos tipos complejos pueden considerarse contenedores de atributos simples que se utilizan como valor de datos en el contexto de DynamoDB. 

En DynamoDB se denominan *atributos anidados* a las propiedades de valor de elementos anidados, como mapas, listas o beans. En la [Guía para desarrolladores de Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes) se denomina *tipo de documento* al formato guardado de un mapa de Java, lista o bean. Los atributos simples que se utilizan por su valor de datos en Java se denominan *tipos escalares* en DynamoDB. Los conjuntos que contienen varios elementos escalares del mismo tipo se denominan *tipos de conjuntos*. 

Es importante saber que la API DynamoDB Enhanced Client convierte una propiedad que es bean en un tipo de documento de mapa de DynamoDB al guardarla.

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

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

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

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

    public String getFirstName() {
        return firstName;
    }

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

    public String getLastName() {
        return lastName;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Address getMainAddress() {
        return mainAddress;
    }

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

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

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

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

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

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

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

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

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

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

    public Address() {
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

    public String getType() {
        return type;
    }

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

    public String getNumber() {
        return number;
    }

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

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

## Almacenamiento de tipos complejos
<a name="ddb-en-client-adv-features-nested-mapping"></a>

### Uso de clases de datos anotadas
<a name="ddb-en-client-adv-features-nested-map-anno"></a>

Si desea guardar atributos anidados para clases personalizadas, puede anotarlos simplemente. La clase `Address` y la clase `PhoneNumber` mostradas anteriormente se anotan solo con la anotación `@DynamoDbBean`. Cuando la API del cliente mejorado de DynamoDB construye el esquema de tabla para la clase `Person` con el siguiente fragmento, la API descubre el uso de las clases `Address` y `PhoneNumber`, y construye las asignaciones correspondientes para trabajar con DynamoDB.

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

### Uso de esquemas abstractos con generadores
<a name="ddb-en-client-adv-features-nested-map-builder"></a>

El método alternativo consiste en utilizar compiladores de esquemas de tablas estáticas para cada clase de bean anidada, como se muestra en el código siguiente.

Los esquemas de tabla de las clases `Address` y `PhoneNumber` y son abstractos en el sentido de que no se pueden usar con una tabla de DynamoDB. Esto se debe a que carecen de definiciones para la clave principal. Sin embargo, se utilizan como esquemas anidados en el esquema de tabla de la clase `Person`.

Tras las líneas de comentario 1 y 2 de la definición de `PERSON_TABLE_SCHEMA`, verá el código que utiliza los esquemas de tabla abstractos. El uso de `documentOf` en el método `EnhanceType.documentOf(...)` no indica que este devuelva un tipo `EnhancedDocument` de la API de documentos mejorados. En este contexto, el método `documentOf(...)` devuelve un objeto que sabe cómo asignar su argumento de clase a y desde los atributos de la tabla de DynamoDB mediante el argumento del esquema de la tabla.

#### Código de esquema estático
<a name="ddb-en-client-adv-features-nested-map-builder-code"></a>

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

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

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

## Atributos de proyecto de tipos complejos
<a name="ddb-en-client-adv-features-nested-projection"></a>

Para los métodos `query()` y `scan()`, puede especificar qué atributos desea que se devuelvan en los resultados mediante llamadas a métodos como `addNestedAttributeToProject()` y `attributesToProject()`. La API del cliente mejorada de DynamoDB convierte los parámetros de llamada al método Java [en expresiones de proyección](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html) antes de enviar la solicitud.

En el siguiente ejemplo, se rellena la tabla `Person` con dos elementos y, a continuación, se efectúan tres operaciones de análisis. 

El primer análisis accede a todos los elementos de la tabla para comparar los resultados con las demás operaciones de análisis. 

El segundo análisis aplica el método del constructor [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)) para devolver solo el valor del atributo `street`.

La tercera operación de análisis sigue el método del constructor [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)) para devolver los datos del atributo de primer nivel, `hobbies`. El tipo de atributo `hobbies` es una lista Para acceder a cada elemento de la lista, haga una operación `get()` en la lista.

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

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

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

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

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

        hobbiesScanResult.items().forEach((p) -> {
            // 3. Log the results of the request for the 'hobbies' attribute.
            logger.info(p.toString());
            // To access an item in a list, first get the parent attribute, 'hobbies', then access items in the list.
            String hobby = p.getHobbies().get(1);
            // 4. Log an item in the list.
            logger.info(hobby);
        });
```

```
// Logged results from comment line 1.
Person{id=2, firstName='first name 2', lastName='last name 2', age=11, addresses={work=Address{street='street 21', city='city 21', state='state 21', zipCode='33333'}, home=Address{street='street 2', city='city 2', state='state 2', zipCode='22222'}}, phoneNumbers=[PhoneNumber{type='home', number='222-222-2222'}, PhoneNumber{type='work', number='333-333-3333'}], hobbies=[hobby 2, hobby 21]}
Person{id=1, firstName='first name 1', lastName='last name 1', age=11, addresses={work=Address{street='street 11', city='city 11', state='state 11', zipCode='22222'}, home=Address{street='street 1', city='city 1', state='state 1', zipCode='11111'}}, phoneNumbers=[PhoneNumber{type='home', number='111-111-1111'}, PhoneNumber{type='work', number='222-222-2222'}], hobbies=[hobby 1, hobby 11]}

// Logged results from comment line 2.
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}

// Logged results from comment lines 3 and 4.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
hobby 21
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
hobby 11
```

**nota**  
Si el método `attributesToProject()` sigue cualquier otro procedimiento de creación que añada los atributos que desee proyectar, la lista de nombres de atributos proporcionada a `attributesToProject()` sustituirá a todos los demás nombres de atributos.  
Un análisis efectuado con la instancia `ScanEnhancedRequest` del fragmento siguiente devuelve solo datos de hobbies.  

```
ScanEnhancedRequest lastOverwrites = ScanEnhancedRequest.builder()
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("firstName")
        // If the 'attributesToProject()' method follows other builder methods that add attributes for projection,
        // its list of attributes replace all previous attributes.
        .attributesToProject("hobbies")
        .build();
PageIterable<Person> hobbiesOnlyResult = personDynamoDbTable.scan(lastOverwrites);
hobbiesOnlyResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```
El siguiente fragmento de código usa primero el método `attributesToProject()`. Este orden preserva todos los demás atributos solicitados.  

```
ScanEnhancedRequest attributesPreserved = ScanEnhancedRequest.builder()
        // Use 'attributesToProject()' first so that the method call does not replace all other attributes
        // that you want to project.
        .attributesToProject("firstName")
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("hobbies")
        .build();
PageIterable<Person> allAttributesResult = personDynamoDbTable.scan(attributesPreserved);
allAttributesResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='first name 2', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='first name 1', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```

## Uso de tipos complejos en expresiones
<a name="ddb-en-client-adv-features-nested-expressions"></a>

Puede usar tipos complejos en expresiones, como expresiones de filtro y expresiones de condición, mediante operadores de cancelación de referencias para navegar por la estructura del tipo complejo. Para objetos y mapas, utilice `. (dot)` y para elementos de lista, utilice `[n]` (corchetes en el número de secuencia del elemento). No puede hacer referencia a elementos individuales de un conjunto, pero puede utilizar la [función `contains`](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions).

El siguiente ejemplo muestra dos expresiones de filtro que se utilizan en operaciones de análisis. Las expresiones de filtro especifican las condiciones de coincidencia de los elementos que desea incluir en los resultados. En el ejemplo se utilizan las clases `Person`, `Address` y `PhoneNumber` mostradas anteriormente.

```
    public void scanUsingFilterOfNestedAttr() {
        // The following is a filter expression for an attribute that is a map of Address objects.
        // By using this filter expression, the SDK returns Person objects that have an address
        // with 'mailing' as a key and 'MS2' for a state value.
        Expression addressFilter = Expression.builder()
                .expression("addresses.#type.#field = :value")
                .putExpressionName("#type", "mailing")
                .putExpressionName("#field", "state")
                .putExpressionValue(":value", AttributeValue.builder().s("MS2").build())
                .build();

        PageIterable<Person> addressFilterResults = personDynamoDbTable.scan(rb -> rb.
                filterExpression(addressFilter));
        addressFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert addressFilterResults.items().stream().count() == 1;


        // The following is a filter expression for an attribute that is a list of phone numbers.
        // By using this filter expression, the SDK returns Person objects whose second phone number
        // in the list has a type equal to 'cell'.
        Expression phoneFilter = Expression.builder()
                .expression("phoneNumbers[1].#type = :type")
                .putExpressionName("#type", "type")
                .putExpressionValue(":type", AttributeValue.builder().s("cell").build())
                .build();

        PageIterable<Person> phoneFilterResults = personDynamoDbTable.scan(rb -> rb
                .filterExpression(phoneFilter)
                .attributesToProject("id", "firstName", "lastName", "phoneNumbers")
        );

        phoneFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert phoneFilterResults.items().stream().count() == 1;
        assert phoneFilterResults.items().stream().findFirst().get().getPhoneNumbers().get(1).getType().equals("cell");
    }
```

### Método auxiliar que rellena la tabla
<a name="nested-expressions-helper-method"></a>

```
    public static void populateDatabase() {
        Person person1 = new Person();
        person1.setId(1);
        person1.setFirstName("FirstName1");
        person1.setLastName("LastName1");

        Address billingAddr1 = new Address();
        billingAddr1.setState("BS1");
        billingAddr1.setCity("BillingTown1");

        Address mailing1 = new Address();
        mailing1.setState("MS1");
        mailing1.setCity("MailingTown1");

        person1.setAddresses(Map.of("billing", billingAddr1, "mailing", mailing1));

        PhoneNumber pn1_1 = new PhoneNumber();
        pn1_1.setType("work");
        pn1_1.setNumber("111-111-1111");

        PhoneNumber pn1_2 = new PhoneNumber();
        pn1_2.setType("home");
        pn1_2.setNumber("222-222-2222");

        List<PhoneNumber> phoneNumbers1 = List.of(pn1_1, pn1_2);
        person1.setPhoneNumbers(phoneNumbers1);

        personDynamoDbTable.putItem(person1);

        Person person2 = person1;
        person2.setId(2);
        person2.setFirstName("FirstName2");
        person2.setLastName("LastName2");

        Address billingAddress2 = billingAddr1;
        billingAddress2.setCity("BillingTown2");
        billingAddress2.setState("BS2");

        Address mailing2 = mailing1;
        mailing2.setCity("MailingTown2");
        mailing2.setState("MS2");

        person2.setAddresses(Map.of("billing", billingAddress2, "mailing", mailing2));

        PhoneNumber pn2_1 = new PhoneNumber();
        pn2_1.setType("work");
        pn2_1.setNumber("333-333-3333");

        PhoneNumber pn2_2 = new PhoneNumber();
        pn2_2.setType("cell");
        pn2_2.setNumber("444-444-4444");

        List<PhoneNumber> phoneNumbers2 = List.of(pn2_1, pn2_2);
        person2.setPhoneNumbers(phoneNumbers2);

        personDynamoDbTable.putItem(person2);
    }
```

### Representación en JSON de elementos de la base de datos
<a name="nested-attributes-expression-json-items"></a>

```
{
 "id": 1,
 "addresses": {
  "billing": {
   "city": "BillingTown1",
   "state": "BS1",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown1",
   "state": "MS1",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName1",
 "lastName": "LastName1",
 "phoneNumbers": [
  {
   "number": "111-111-1111",
   "type": "work"
  },
  {
   "number": "222-222-2222",
   "type": "home"
  }
 ]
}

{
 "id": 2,
 "addresses": {
  "billing": {
   "city": "BillingTown2",
   "state": "BS2",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown2",
   "state": "MS2",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName2",
 "lastName": "LastName2",
 "phoneNumbers": [
  {
   "number": "333-333-3333",
   "type": "work"
  },
  {
   "number": "444-444-4444",
   "type": "cell"
  }
 ]
}
```

## Actualización de elementos que contienen tipos complejos
<a name="ddb-en-client-adv-features-nested-updates"></a>

Para actualizar un elemento que contiene tipos complejos hay dos métodos básicos:
+ Método 1: recupere primero el elemento (usando `getItem`), actualice el objeto y, a continuación, llame a `DynamoDbTable#updateItem`.
+ Método 2: no recupere el elemento, pero cree una nueva instancia, establezca las propiedades que desee actualizar y envíe la instancia a `DynamoDbTable#updateItem` estableciendo el valor apropiado de [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html). Con este método no es necesario recuperar el elemento antes de actualizarlo.

Los ejemplos que se muestran en esta sección utilizan las clases `Person`, `Address` y `PhoneNumber` mostradas anteriormente.

### Método de actualización 1: recupere y, a continuación, actualice
<a name="ddb-en-client-adv-features-nested-updates-retreive"></a>

Al utilizar este método, se asegura de que no se pierdan datos durante la actualización. La API DynamoDB Enhanced Client vuelve a crear el bean con los atributos del elemento guardado en DynamoDB, incluidos los valores de tipos complejos. A continuación, debe usar los métodos getters y los setters para actualizar el bean. La desventaja de este método es el costo en el que se incurre al recuperar primero el elemento.

El siguiente ejemplo demuestra que no se pierde ningún dato si se recupera el elemento antes de actualizarlo.

```
    public void retrieveThenUpdateExample()  {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");
        person.setLastName("LastName");

        Address mainAddress = new Address();
        mainAddress.setStreet("123 MyStreet");
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        mainAddress.setZipCode("MyZipCode");
        person.setMainAddress(mainAddress);

        PhoneNumber homePhone = new PhoneNumber();
        homePhone.setNumber("1111111");
        homePhone.setType("HOME");
        person.setPhoneNumbers(List.of(homePhone));

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.
        // First, retrieve the item
        Person retrievedPerson = personDynamoDbTable.getItem(Key.builder().partitionValue(1).build());

        // Make any updates.
        retrievedPerson.getMainAddress().setCity("YourCity");

        // Save the updated bean. 'updateItem' returns the bean as it appears after the update.
        Person updatedPerson = personDynamoDbTable.updateItem(retrievedPerson);

        // Verify for this example.
        Address updatedMainAddress = updatedPerson.getMainAddress();
        assert updatedMainAddress.getCity().equals("YourCity");
        assert updatedMainAddress.getState().equals("MyState"); // Unchanged.
        // The list of phone numbers remains; it was not set to null;
        assert updatedPerson.getPhoneNumbers().size() == 1;
    }
```

### Método de actualización 2: utilice una enumeración `IgnoreNullsMode` sin recuperar primero el elemento
<a name="ddb-en-client-adv-features-nested-updates-nullmode"></a>

Para actualizar un elemento en DynamoDB, puede proporcionar un objeto nuevo que solo tenga las propiedades que desee actualizar y dejar los demás valores como nulos. Con este método, debe saber cómo trata el SDK los valores nulos del objeto y cómo puede controlar su comportamiento.

Para especificar qué propiedades con valores nulos quiere que ignore el SDK, proporcione una enumeración `IgnoreNullsMode` al compilar [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html). Como ejemplo de uso de uno de los valores enumerados, en el siguiente fragmento se utiliza el modo `IgnoreNullsMode.SCALAR_ONLY`.

```
// Create a new Person object to update the existing item in DynamoDB.
Person personForUpdate = new Person();
personForUpdate.setId(1);
personForUpdate.setFirstName("updatedFirstName");  // 'firstName' is a top scalar property.

Address addressForUpdate = new Address();
addressForUpdate.setCity("updatedCity");
personForUpdate.setMainAddress(addressForUpdate);

personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY));

/* With IgnoreNullsMode.SCALAR_ONLY provided, The SDK ignores all null properties. The SDK adds or replaces
the 'firstName' property with the provided value, "updatedFirstName". The SDK updates the 'city' value of
'mainAddress', as long as the 'mainAddress' attribute already exists in DynamoDB.

In the background, the SDK generates an update expression that it sends in the request to DynamoDB.
The following JSON object is a simplified version of what it sends. Notice that the SDK includes the paths
to 'mainAddress.city' and 'firstName' in the SET clause of the update expression. No null values in
'personForUpdate' are included.

{
  "TableName": "PersonTable",
  "Key": {
    "id": {
      "N": "1"
    }
  },
  "ReturnValues": "ALL_NEW",
  "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city, #firstName = :firstName",
  "ExpressionAttributeNames": {
    "#city": "city",
    "#firstName": "firstName",
    "#mainAddress": "mainAddress"
  },
  "ExpressionAttributeValues": {
    ":firstName": {
      "S": "updatedFirstName"
    },
    ":mainAddress_city": {
      "S": "updatedCity"
    }
  }
}

Had we chosen 'IgnoreNullsMode.DEFAULT' instead of 'IgnoreNullsMode.SCALAR_ONLY', the SDK would have included
null values in the "ExpressionAttributeValues" section of the request as shown in the following snippet.

  "ExpressionAttributeValues": {
    ":mainAddress": {
      "M": {
        "zipCode": {
          "NULL": true
        },
        "city": {
          "S": "updatedCity"
        },
        "street": {
          "NULL": true
        },
        "state": {
          "NULL": true
        }
      }
    },
    ":firstName": {
      "S": "updatedFirstName"
    }
  }
*/
```

Encontrará más información sobre [expresiones de actualización](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html) en la Guía para desarrolladores de Amazon DynamoDB.

#### Descripción de las opciones de `IgnoreNullsMode`
<a name="ignore-nulls-mode-descriptions"></a>
+ `IgnoreNullsMode.SCALAR_ONLY`: use esta configuración para actualizar atributos escalares en cualquier nivel. El SDK crea una instrucción de actualización que envía solo atributos escalares no nulos a DynamoDB. El SDK ignora los atributos escalares con valores nulos de un bean o mapa y conserva el valor guardado en DynamoDB.

  Al actualizar un atributo escalar de un mapa o bean, el mapa ya debe existir en DynamoDB. Si agrega un mapa o un bean al objeto que aún no existe para el objeto en DynamoDB, se generará una excepción `DynamoDbException` con el mensaje *The document path provided in the update expression is invalid for update*. Debe usar el modo `MAPS_ONLY` para añadir un bean o un mapa a DynamoDB antes de actualizar cualquiera de sus atributos.
+ `IgnoreNullsMode.MAPS_ONLY`: utilice este valor para añadir o reemplazar propiedades que sean un bean o un mapa. El SDK reemplaza o añade cualquier mapa o bean incluido en el objeto. Se ignoran los beans o mapas que son nulos en el objeto y se conserva el mapa que existe en DynamoDB.
+ `IgnoreNullsMode.DEFAULT`: con esta configuración, el SDK nunca ignora los valores nulos. Los atributos escalares de cualquier nivel que sean nulos se actualizan a nulos. El SDK actualiza cualquier propiedad de bean, mapa, lista o conjunto con valores nulos en el objeto a nulos en DynamoDB. Cuando utilice este modo (o no proporcione un modo, ya que es el modo predeterminado), debe recuperar primero el elemento para que los valores de DynamoDB no se establezcan como nulos si ya están presentes en el objeto que se va a actualizar, a menos que su intención sea establecerlos como nulos.

En todos los modos, si proporciona a `updateItem` un objeto que tiene una lista o un conjunto no nulos, la lista o el conjunto se guardan en DynamoDB. 

#### ¿Por qué los modos?
<a name="ddb-en-client-adv-features-nested-updates-nullmodes-why"></a>

Al proporcionar un objeto con un bean o un mapa al `updateItem` método, el SDK no puede determinar si debe usar los valores de las propiedades del bean (o los valores de entrada del mapa) para actualizar el elemento o si todo bean/map debe reemplazar lo que se ha guardado en DynamoDB.

Siguiendo el ejemplo anterior, en el que se muestra primero la recuperación del elemento, intentemos actualizar el atributo `city` de `mainAddress` sin la recuperación.

```
/* The retrieval example saved the Person object with a 'mainAddress' property whose 'city' property value is "MyCity".
/* Note that we create a new Person with only the necessary information to update the city value
of the mainAddress. */
Person personForUpdate = new Person();
personForUpdate.setId(1);
// The update we want to make changes the city.
Address mainAddressForUpdate = new Address();
mainAddressForUpdate.setCity("YourCity");
personForUpdate.setMainAddress(mainAddressForUpdate);

// Lets' try the following:
Person updatedPerson = personDynamoDbTable.updateItem(personForUpdate);
/*
 Since we haven't retrieved the item, we don't know if the 'mainAddress' property
 already exists, so what update expression should the SDK generate?

A) Should it replace or add the 'mainAddress' with the provided object (setting all attributes to null other than city)
   as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress = :mainAddress",
        "ExpressionAttributeNames": {
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress": {
            "M": {
              "zipCode": {
                "NULL": true
              },
              "city": {
                "S": "YourCity"
              },
              "street": {
                "NULL": true
              },
              "state": {
                "NULL": true
              }
            }
          }
        }
      }
 
B) Or should it update only the 'city' attribute of an existing 'mainAddress' as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city",
        "ExpressionAttributeNames": {
          "#city": "city",
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress_city": {
            "S": "YourCity"
          }
        }
      }

However, assume that we don't know if the 'mainAddress' already exists. If it doesn't exist, the SDK would try to update 
an attribute of a non-existent map, which results in an exception.

In this particular case, we would likely select option B (SCALAR_ONLY) to retain the other values of the 'mainAddress'.
*/
```

En los dos ejemplos siguientes se muestran los usos de los valores enumerados `MAPS_ONLY` y `SCALAR_ONLY`. `MAPS_ONLY` agrega un mapa y `SCALAR_ONLY` actualiza un mapa.

##### `IgnoreNullsMode.MAPS_ONLY`Ejemplo de
<a name="scalar-only-example"></a>

```
    public void mapsOnlyModeExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);


        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); // Since the mainAddress property does not exist, use MAPS_ONLY mode.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState() == null;
    }
```

##### `IgnoreNullsMode.SCALAR_ONLY example`
<a name="maps-only-example"></a>

```
    public void scalarOnlyExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        Address mainAddress = new Address();
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        person.setMainAddress(mainAddress);

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);

        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); // SCALAR_ONLY mode ignores null properties in the in mainAddress.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState().equals("MyState"); // The state property remains the same.
    }
```

Consulte la siguiente tabla para ver qué valores nulos se ignoran para cada modo. A menudo puede trabajar con `SCALAR_ONLY` o con `MAPS_ONLY`, salvo cuando trabaja con beans o mapas.


**¿Qué propiedades con valores nulos en el objeto enviado a `updateItem` ignora el SDK para cada modo?**  

| Tipo de propiedad | en modo SCALAR\$1ONLY | en modo MAPS\$1ONLY | en modo DEFAULT | 
| --- | --- | --- | --- | 
| Escalar superior | Sí | Sí | No | 
| Bean o mapa | Sí | Sí | No | 
| Valor escalar de una entrada de bean o mapa | Sí1 | No2 | No | 
| Lista o conjunto | Sí | Sí | No | 

1Asume que el mapa ya existe en DynamoDB. Cualquier valor escalar (nulo o no nulo) del bean o el mapa que proporcione en el objeto para actualización requiere que exista una ruta al valor en DynamoDB. El SDK crea una ruta al atributo mediante el operador de desreferenciación `. (dot)` antes de enviar la solicitud.

2Dado que se utiliza el modo `MAPS_ONLY` para sustituir por completo o añadir un bean o mapa, todos los valores nulos del bean o mapa se conservan en el mapa guardado en DynamoDB.

# Conserva los objetos vacíos con `@DynamoDbPreserveEmptyObject`
<a name="ddb-en-client-adv-features-empty"></a>

Si guarda un bean en Amazon DynamoDB con objetos vacíos y desea que el SDK vuelva a crear los objetos vacíos al recuperarlos, anote el getter del bean interior con `@DynamoDbPreserveEmptyObject`.

Para ilustrar cómo funciona la anotación, en el ejemplo de código se utilizan los dos beans siguientes.

## Beans de ejemplo
<a name="ddb-en-client-adv-features-empty-ex1"></a>

La clase de datos siguiente contiene dos campos `InnerBean`. El método de getter, `getInnerBeanWithoutAnno()`, no está anotado con `@DynamoDbPreserveEmptyObject`. El método `getInnerBeanWithAnno()` está anotado.

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithAnno;

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

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

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbPreserveEmptyObject
    public InnerBean getInnerBeanWithAnno() { return innerBeanWithAnno; }
    public void setInnerBeanWithAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithAnno=" + innerBeanWithAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

Las instancias de la clase siguiente `InnerBean` son campos de `MyBean` y se inicializan como objetos vacíos en el código de ejemplo.

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanField;

    public String getInnerBeanField() {
        return innerBeanField;
    }

    public void setInnerBeanField(String innerBeanField) {
        this.innerBeanField = innerBeanField;
    }

    @Override
    public String toString() {
        return "InnerBean{" +
                "innerBeanField='" + innerBeanField + '\'' +
                '}';
    }
}
```

El siguiente ejemplo de código guarda un objeto `MyBean` con objetos beans internos inicializados en DynamoDB y, a continuación, recupera el elemento. El resultado registrado muestra que el `innerBeanWithoutAnno` no se ha inicializado, sino que se ha creado `innerBeanWithAnno`.

```
    public MyBean preserveEmptyObjectAnnoUsingGetItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Save an item to DynamoDB.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(new InnerBean());   // Instantiate the inner bean.
        bean.setInnerBeanWithAnno(new InnerBean());      // Instantiate the inner bean.
        myBeanTable.putItem(bean);

        GetItemEnhancedRequest request = GetItemEnhancedRequest.builder()
                .key(Key.builder().partitionValue("1").build())
                .build();
        MyBean myBean = myBeanTable.getItem(request);

        logger.info(myBean.toString());
        // Output 'MyBean[innerBeanWithoutAnno=null, innerBeanWithAnno=InnerBean{innerBeanField='null'}, id='1', name='null']'.

        return myBean;
    }
```

## Esquema estático alternativo
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

Puede utilizar la siguiente versión de `StaticTableSchema` de los esquemas de tabla en lugar de las anotaciones de los beans.

```
    public static TableSchema<MyBean> buildStaticSchemas() {

        StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
                StaticTableSchema.builder(InnerBean.class)
                        .newItemSupplier(InnerBean::new)
                        .addAttribute(String.class, a -> a.name("innerBeanField")
                                .getter(InnerBean::getInnerBeanField)
                                .setter(InnerBean::setInnerBeanField))
                        .build();

        return StaticTableSchema.builder(MyBean.class)
                .newItemSupplier(MyBean::new)
                .addAttribute(String.class, a -> a.name("id")
                        .getter(MyBean::getId)
                        .setter(MyBean::setId)
                        .addTag(primaryPartitionKey()))
                .addAttribute(String.class, a -> a.name("name")
                        .getter(MyBean::getName)
                        .setter(MyBean::setName))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema),
                        a -> a.name("innerBean1")
                                .getter(MyBean::getInnerBeanWithoutAnno)
                                .setter(MyBean::setInnerBeanWithoutAnno))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema,
                                b -> b.preserveEmptyObject(true)),
                        a -> a.name("innerBean2")
                                .getter(MyBean::getInnerBeanWithAnno)
                                .setter(MyBean::setInnerBeanWithAnno))
                .build();
    }
```

# Evitar guardar atributos nulos de los objetos anidados
<a name="ddb-en-client-adv-features-ignore-null"></a>

Puede omitir los atributos nulos de los objetos anidados al guardar un objeto de clase de datos en DynamoDB aplicando la anotación. `@DynamoDbIgnoreNulls` Por el contrario, los atributos de nivel superior con valores nulos nunca se guardan en la base de datos.

Para ilustrar cómo funciona la anotación, en el ejemplo de código se utilizan los dos beans siguientes.

## Beans de ejemplo
<a name="ddb-en-client-adv-features-ignore-null-ex1"></a>

La clase de datos siguiente contiene dos campos `InnerBean`. El método de getter, `getInnerBeanWithoutAnno()`, no está anotado. El método `getInnerBeanWithIgnoreNullsAnno()` está anotado con `@DynamoDbIgnoreNulls`.

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithIgnoreNullsAnno;

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

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

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbIgnoreNulls
    public InnerBean getInnerBeanWithIgnoreNullsAnno() { return innerBeanWithIgnoreNullsAnno; }
    public void setInnerBeanWithIgnoreNullsAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithIgnoreNullsAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithIgnoreNullsAnno=" + innerBeanWithIgnoreNullsAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

Las instancias de la clase `InnerBean` siguiente son campos de `MyBean` y se inicializan como objetos vacíos en el código de ejemplo.

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanFieldString;
    private Integer innerBeanFieldInteger;

    public String getInnerBeanFieldString() { return innerBeanFieldString; }
    public void setInnerBeanFieldString(String innerBeanFieldString) { this.innerBeanFieldString = innerBeanFieldString; }

    public Integer getInnerBeanFieldInteger() { return innerBeanFieldInteger; }
    public void setInnerBeanFieldInteger(Integer innerBeanFieldInteger) { this.innerBeanFieldInteger = innerBeanFieldInteger; }

    @Override
    public String toString() {
        return new StringJoiner(", ", InnerBean.class.getSimpleName() + "[", "]")
                .add("innerBeanFieldString='" + innerBeanFieldString + "'")
                .add("innerBeanFieldInteger=" + innerBeanFieldInteger)
                .toString();
    }
}
```

El siguiente ejemplo de código crea un objeto `InnerBean` y establece solo uno de sus dos atributos con un valor. 

```
    public void ignoreNullsAnnoUsingPutItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Create an InnerBean object and give only one attribute a value.
        InnerBean innerBeanOneAttributeSet = new InnerBean();
        innerBeanOneAttributeSet.setInnerBeanFieldInteger(200);

        // Create a MyBean instance and use the same InnerBean instance both for attributes.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(innerBeanOneAttributeSet);
        bean.setInnerBeanWithIgnoreNullsAnno(innerBeanOneAttributeSet);

        Map<String, AttributeValue> itemMap = myBeanTable.tableSchema().itemToMap(bean, true);
        logger.info(itemMap.toString());
        // Log the map that is sent to the database.
        // {innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)}), id=AttributeValue(S=1), innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})}
        
        // Save the MyBean object to the table.
        myBeanTable.putItem(bean);
    }
```

Para visualizar los datos de bajo nivel que se envían a DynamoDB, el código registra el mapa de atributos antes de guardar el objeto `MyBean`.

La salida registrada muestra que `innerBeanWithIgnoreNullsAnno` emite un atributo,

```
innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)})
```

La instancia `innerBeanWithoutAnno` genera dos atributos. Un atributo tiene un valor de 200 y el otro tiene un valor nulo.

```
innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})
```

## Representación en JSON del mapa de atributos
<a name="ddb-en-client-adv-features-ignore-null-ex2"></a>

La siguiente representación en JSON facilita la visualización de los datos guardados en DynamoDB.

```
{
  "id": {
    "S": "1"
  },
  "innerBeanWithIgnoreNullsAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      }
    }
  },
  "innerBeanWithoutAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      },
      "innerBeanFieldString": {
        "NULL": true
      }
    }
  }
}
```

## Esquema estático alternativo
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

Puede utilizar la siguiente versión de `StaticTableSchema` de los esquemas de tabla en lugar de las anotaciones de los beans.

```
public static TableSchema<MyBean> buildStaticSchemas() {

    StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
        StaticTableSchema.builder(InnerBean.class)
            .newItemSupplier(InnerBean::new)
            .addAttribute(String.class, a -> a.name("innerBeanFieldString")
                .getter(InnerBean::getInnerBeanFieldString)
                .setter(InnerBean::setInnerBeanFieldString))
            .addAttribute(Integer.class, a -> a.name("innerBeanFieldInteger")
                .getter(InnerBean::getInnerBeanFieldInteger)
                .setter(InnerBean::setInnerBeanFieldInteger))
            .build();

    return StaticTableSchema.builder(MyBean.class)
        .newItemSupplier(MyBean::new)
        .addAttribute(String.class, a -> a.name("id")
            .getter(MyBean::getId)
            .setter(MyBean::setId)
            .addTag(primaryPartitionKey()))
        .addAttribute(String.class, a -> a.name("name")
            .getter(MyBean::getName)
            .setter(MyBean::setName))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema),
            a -> a.name("innerBeanWithoutAnno")
                .getter(MyBean::getInnerBeanWithoutAnno)
                .setter(MyBean::setInnerBeanWithoutAnno))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema,
                b -> b.ignoreNulls(true)),
            a -> a.name("innerBeanWithIgnoreNullsAnno")
                .getter(MyBean::getInnerBeanWithIgnoreNullsAnno)
                .setter(MyBean::setInnerBeanWithIgnoreNullsAnno))
        .build();
}
```

# Trabaje con documentos JSON con la API de documentos mejorada para DynamoDB
<a name="ddb-en-client-doc-api"></a>

La [API de documentos mejorada](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html) para AWS SDK for Java 2.x está diseñada para funcionar con datos orientados a documentos que no tienen un esquema fijo. Sin embargo, también le permite utilizar clases personalizadas para asignar atributos individuales.

 La API de documentos mejorada es la sucesora de la [API de documentos](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/DynamoDB.html) de la AWS SDK para Java versión 1.x.

**Contents**
+ [Introducción al uso de API de documentos mejorada](ddb-en-client-doc-api-steps.md)
  + [Crear un `DocumentTableSchema` y una `DynamoDbTable`.](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema)
+ [Crear documentos mejorados](ddb-en-client-doc-api-steps-create-ed.md)
  + [Compilar a partir de una cadena JSON](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromJson)
  + [Construir a partir de elementos individuales](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)
+ [Operaciones CRUD](ddb-en-client-doc-api-steps-use.md)
+ [Acceder a los atributos mejorados del documento como objetos personalizados](ddb-en-client-doc-api-convert.md)
+ [Utilizar un `EnhancedDocument` sin DynamoDB](ddb-en-client-doc-api-standalone.md)

# Introducción al uso de API de documentos mejorada
<a name="ddb-en-client-doc-api-steps"></a>

La API de documentos mejorada requiere las mismas [dependencias](ddb-en-client-getting-started.md#ddb-en-client-gs-dep) que se necesitan para la API de cliente mejorado de DynamoDB. También requiere una [instancia de `DynamoDbEnhancedClient`](ddb-en-client-getting-started-dynamodbTable.md#ddb-en-client-getting-started-dynamodbTable-eclient), como se muestra al principio de este tema.

Como la API de documentos mejorada se publicó con la versión 2.20.3 de AWS SDK for Java 2.x, necesita esa versión o una superior.

## Crear un `DocumentTableSchema` y una `DynamoDbTable`.
<a name="ddb-en-client-doc-api-steps-createschema"></a>

Para invocar comandos en una tabla de DynamoDB mediante la API de documentos mejorada, asocie la tabla a un objeto de recurso < > del lado del [DynamoDbTablecliente EnhancedDocument](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html). 

El método `table()` del cliente mejorado crea una instancia `DynamoDbTable<EnhancedDocument>` y requiere parámetros para el nombre de la tabla de DynamoDB y un `DocumentTableSchema`. 

El generador de a [DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)requiere una clave de índice principal y uno o más proveedores de convertidores de atributos. El método `AttributeConverterProvider.defaultProvider()` proporciona convertidores para los [tipos predeterminados](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/package-summary.html). Debe especificarse incluso si proporciona un proveedor de convertidores de atributos personalizado. Puede añadir una clave de índice secundaria opcional al generador.

El siguiente fragmento de código muestra el código que genera la representación del lado de una tabla `person` de DynamoDB que almacena objetos `EnhancedDocument` sin esquema.

```
DynamoDbTable<EnhancedDocument> documentDynamoDbTable = 
                enhancedClient.table("person",
                        TableSchema.documentSchemaBuilder()
                            // Specify the primary key attributes.
                            .addIndexPartitionKey(TableMetadata.primaryIndexName(),"id", AttributeValueType.S)
                            .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                            // Specify attribute converter providers. Minimally add the default one.
                            .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                            .build());
                                                         
// Call documentTable.createTable() if "person" does not exist in DynamoDB.
// createTable() should be called only one time.
```

A continuación, se muestra la representación JSON de un objeto `person` que se utiliza en esta sección.

### Objeto `person` JSON
<a name="ddb-en-client-doc-api-steps-createschema-obj"></a>

```
{
  "id": 1,
  "firstName": "Richard",
  "lastName": "Roe",
  "age": 25,
  "addresses":
    {
      "home": {
        "zipCode": "00000",
        "city": "Any Town",
        "state": "FL",
        "street": "123 Any Street"
      },
      "work": {
        "zipCode": "00001",
        "city": "Anywhere",
        "state": "FL",
        "street": "100 Main Street"
      }
    },
  "hobbies": [
    "Hobby 1",
    "Hobby 2"
  ],
  "phoneNumbers": [
    {
      "type": "Home",
      "number": "555-0100"
    },
    {
      "type": "Work",
      "number": "555-0119"
    }
  ]
}
```

# Crear documentos mejorados
<a name="ddb-en-client-doc-api-steps-create-ed"></a>

Un `[EnhancedDocument](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html)` representa un objeto de tipo documento que tiene una estructura compleja con atributos anidados. Un `EnhancedDocument` requiere atributos de nivel superior que coincidan con los atributos clave principales especificados para el `DocumentTableSchema`. El contenido restante es arbitrario y puede consistir en atributos de nivel superior y también en atributos profundamente anidados.

Para crear una instancia `EnhancedDocument`, utilice un generador que proporciona varias formas de añadir elementos.

## Compilar a partir de una cadena JSON
<a name="ddb-en-client-doc-api-steps-create-ed-fromJson"></a>

Con una cadena JSON, puede construir un `EnhancedDocument` en una llamada al método. El siguiente fragmento de código crea un `EnhancedDocument` a partir de una cadena JSON devuelta por el método auxiliar `jsonPerson()`. El método `jsonPerson()` devuelve la versión de cadena JSON del [objeto persona](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema-obj) mostrado anteriormente.

```
EnhancedDocument document = 
        EnhancedDocument.builder()
                        .json( jsonPerson() )
                        .build());
```

## Construir a partir de elementos individuales
<a name="ddb-en-client-doc-api-steps-create-ed-fromparts"></a>

Alternativamente, puede construir una instancia `EnhancedDocument` a partir de componentes individuales utilizando métodos seguros de tipo del constructor.

En el ejemplo siguiente, se crea un documento mejorado `person` similar al que se crea a partir de la cadena JSON del ejemplo anterior.

```
        /* Define the shape of an address map whose JSON representation looks like the following.
           Use 'addressMapEnhancedType' in the following EnhancedDocument.builder() to simplify the code.
           "home": {
             "zipCode": "00000",
             "city": "Any Town",
             "state": "FL",
             "street": "123 Any Street"
           }*/
        EnhancedType<Map<String, String>> addressMapEnhancedType =
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class));


        //  Use the builder's typesafe methods to add elements to the enhanced document.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                /* Add the map of addresses whose JSON representation looks like the following.
                        {
                          "home": {
                            "zipCode": "00000",
                            "city": "Any Town",
                            "state": "FL",
                            "street": "123 Any Street"
                          }
                        } */
                .putMap("addresses", getAddresses(), EnhancedType.of(String.class), addressMapEnhancedType)
                .putList("hobbies", List.of("Theater", "Golf"), EnhancedType.of(String.class))
                .build();
```

### Métodos auxiliares
<a name="ddb-en-client-doc-api-steps-use-fromparts-helpers"></a>

```
    private static String phoneNumbersJSONString() {
        return "  [" +
                "    {" +
                "      \"type\": \"Home\"," +
                "      \"number\": \"555-0140\"" +
                "    }," +
                "    {" +
                "      \"type\": \"Work\"," +
                "      \"number\": \"555-0155\"" +
                "    }" +
                "  ]";
    }

    private static Map<String, Map<String, String>> getAddresses() {
        return Map.of(
                "home", Map.of(
                        "zipCode", "00002",
                        "city", "Any Town",
                        "state", "ME",
                        "street", "123 Any Street"));

    }
```

# Operaciones CRUD
<a name="ddb-en-client-doc-api-steps-use"></a>

Una vez definida una instancia `EnhancedDocument`, puede guardarla en una tabla de DynamoDB. El siguiente fragmento de código utiliza el [PersonDocument](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts) que se creó a partir de elementos individuales.

```
documentDynamoDbTable.putItem(personDocument);
```

Después de leer una instancia de documento mejorada de DynamoDB, puede extraer los valores individuales de los atributos utilizando getters como se muestra en el siguiente fragmento de código que accede a los datos guardados del `personDocument`. Como alternativa, puede extraer el contenido completo en una cadena JSON, como se muestra en la última parte del código de ejemplo.

```
        // Read the item.
        EnhancedDocument personDocFromDb = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).build());

        // Access top-level attributes.
        logger.info("Name: {} {}", personDocFromDb.getString("firstName"), personDocFromDb.getString("lastName"));
        // Name: Shirley Rodriguez

        // Typesafe access of a deeply nested attribute. The addressMapEnhancedType shown previously defines the shape of an addresses map.
        Map<String, Map<String, String>> addresses = personDocFromDb.getMap("addresses", EnhancedType.of(String.class), addressMapEnhancedType);
        addresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));
        // {zipCode=00002, city=Any Town, street=123 Any Street, state=ME}

        // Alternatively, work with AttributeValue types checking along the way for deeply nested attributes.
        Map<String, AttributeValue> addressesMap = personDocFromDb.getMapOfUnknownType("addresses");
        addressesMap.keySet().forEach((String k) -> {
            logger.info("Looking at data for [{}] address", k);
            // Looking at data for [home] address
            AttributeValue value = addressesMap.get(k);
            AttributeValue cityValue = value.m().get("city");
            if (cityValue != null) {
                logger.info(cityValue.s());
                // Any Town
            }
        });

        List<AttributeValue> phoneNumbers = personDocFromDb.getListOfUnknownType("phoneNumbers");
        phoneNumbers.forEach((AttributeValue av) -> {
            if (av.hasM()) {
                AttributeValue type = av.m().get("type");
                if (type.s() != null) {
                    logger.info("Type of phone: {}", type.s());
                    // Type of phone: Home
                    // Type of phone: Work
                }
            }
        });

        String jsonPerson = personDocFromDb.toJson();
        logger.info(jsonPerson);
        // {"firstName":"Shirley","lastName":"Rodriguez","addresses":{"home":{"zipCode":"00002","city":"Any Town","street":"123 Any Street","state":"ME"}},"hobbies":["Theater","Golf"],
        //     "id":50,"nullAttribute":null,"age":53,"phoneNumbers":[{"number":"555-0140","type":"Home"},{"number":"555-0155","type":"Work"}]}
```

`EnhancedDocument`las instancias se pueden usar con cualquier método `[DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)` o [DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html)en lugar de las clases de datos mapeadas.

# Acceder a los atributos mejorados del documento como objetos personalizados
<a name="ddb-en-client-doc-api-convert"></a>

Además de proporcionar una API para leer y escribir atributos con estructuras sin esquema, la API de documentos mejorada te permite convertir atributos desde y hacia instancias de clases personalizadas.

La API del cliente mejorado utiliza `AttributeConverterProvider`s y `AttributeConverter`s que se mostraron en la sección de [conversión de atributos de control](ddb-en-client-adv-features-conversion.md) como parte de la API del cliente mejorado de DynamoDB.

En el ejemplo siguiente, utilizamos a `CustomAttributeConverterProvider` con su clase `AddressConverter` anidada para convertir objetos `Address`. 

En este ejemplo se muestra que se pueden mezclar datos de clases y también datos de estructuras que se crean según sea necesario. En este ejemplo también se muestra que las clases personalizadas se pueden utilizar en cualquier nivel de una estructura anidada. Los objetos `Address` de este ejemplo son valores que se utilizan en un mapa.

```
    public static void attributeToAddressClassMappingExample(DynamoDbEnhancedClient enhancedClient, DynamoDbClient standardClient) {
        String tableName = "customer";

        // Define the DynamoDbTable for an enhanced document.
        // The schema builder provides methods for attribute converter providers and keys.
        DynamoDbTable<EnhancedDocument> documentDynamoDbTable = enhancedClient.table(tableName,
                DocumentTableSchema.builder()
                        // Add the CustomAttributeConverterProvider along with the default when you build the table schema.
                        .attributeConverterProviders(
                                List.of(
                                        new CustomAttributeConverterProvider(),
                                        AttributeConverterProvider.defaultProvider()))
                        .addIndexPartitionKey(TableMetadata.primaryIndexName(), "id", AttributeValueType.N)
                        .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                        .build());
        // Create the DynamoDB table if needed.
        documentDynamoDbTable.createTable();
        waitForTableCreation(tableName, standardClient);


        // The getAddressesForCustomMappingExample() helper method that provides 'addresses' shows the use of a custom Address class
        // rather than using a Map<String, Map<String, String> to hold the address data.
        Map<String, Address> addresses = getAddressesForCustomMappingExample();

        // Build an EnhancedDocument instance to save an item with a mix of structures defined as needed and static classes.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                // Note the use of 'EnhancedType.of(Address.class)' instead of the more generic
                // 'EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class))' that was used in a previous example.
                .putMap("addresses", addresses, EnhancedType.of(String.class), EnhancedType.of(Address.class))
                .putList("hobbies", List.of("Hobby 1", "Hobby 2"), EnhancedType.of(String.class))
                .build();
        // Save the item to DynamoDB.
        documentDynamoDbTable.putItem(personDocument);

        // Retrieve the item just saved.
        EnhancedDocument srPerson = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).sortValue("Rodriguez").build());

        // Access the addresses attribute.
        Map<String, Address> srAddresses = srPerson.get("addresses",
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(Address.class)));

        srAddresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));

        documentDynamoDbTable.deleteTable();

// The content logged to the console shows that the saved maps were converted to Address instances.
Address{street='123 Main Street', city='Any Town', state='NC', zipCode='00000'}
Address{street='100 Any Street', city='Any Town', state='NC', zipCode='00000'}
```

## `CustomAttributeConverterProvider` code
<a name="ddb-en-client-doc-api-convert-provider"></a>

```
public class CustomAttributeConverterProvider implements AttributeConverterProvider {

    private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
            // 1. Add AddressConverter to the internal cache.
            EnhancedType.of(Address.class), new AddressConverter());

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

    // 2. The enhanced client queries the provider for attribute converters if it
    //    encounters a type that it does not know how to convert.
    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
        return (AttributeConverter<T>) converterCache.get(enhancedType);
    }

    // 3. Custom attribute converter
    private class AddressConverter implements AttributeConverter<Address> {
        // 4. Transform an Address object into a DynamoDB map.
        @Override
        public AttributeValue transformFrom(Address address) {

            Map<String, AttributeValue> attributeValueMap = Map.of(
                    "street", AttributeValue.fromS(address.getStreet()),
                    "city", AttributeValue.fromS(address.getCity()),
                    "state", AttributeValue.fromS(address.getState()),
                    "zipCode", AttributeValue.fromS(address.getZipCode()));

            return AttributeValue.fromM(attributeValueMap);
        }

        // 5. Transform the DynamoDB map attribute to an Address oject.
        @Override
        public Address transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> m = attributeValue.m();
            Address address = new Address();
            address.setStreet(m.get("street").s());
            address.setCity(m.get("city").s());
            address.setState(m.get("state").s());
            address.setZipCode(m.get("zipCode").s());

            return address;
        }

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

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

## Clase `Address`
<a name="ddb-en-client-doc-api-convert-address"></a>

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

                  public Address() {
                  }

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

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

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

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

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

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

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

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

## Método auxiliar que proporciona direcciones
<a name="ddb-en-client-doc-api-convert-helper"></a>

El siguiente método auxiliar proporciona el mapa que utiliza instancias `Address` personalizadas para los valores en lugar de instancias `Map<String, String>` genéricas para los valores.

```
    private static Map<String, Address> getAddressesForCustomMappingExample() {
        Address homeAddress = new Address();
        homeAddress.setStreet("100 Any Street");
        homeAddress.setCity("Any Town");
        homeAddress.setState("NC");
        homeAddress.setZipCode("00000");

        Address workAddress = new Address();
        workAddress.setStreet("123 Main Street");
        workAddress.setCity("Any Town");
        workAddress.setState("NC");
        workAddress.setZipCode("00000");

        return Map.of("home", homeAddress,
                "work", workAddress);
    }
```

# Utilizar un `EnhancedDocument` sin DynamoDB
<a name="ddb-en-client-doc-api-standalone"></a>

Aunque normalmente se utiliza una instancia de un `EnhancedDocument` para leer y escribir elementos de DynamoDB de tipo documento, también se puede utilizar con independencia de DynamoDB. 

Puede utilizar `EnhancedDocuments` por su capacidad de convertir cadenas JSON u objetos personalizados en mapas de `AttributeValues` de bajo nivel, como se muestra en el siguiente ejemplo.

```
    public static void conversionWithoutDynamoDbExample() {
        Address address = new Address();
        address.setCity("my city");
        address.setState("my state");
        address.setStreet("my street");
        address.setZipCode("00000");

        // Build an EnhancedDocument instance for its conversion functionality alone.
        EnhancedDocument addressEnhancedDoc = EnhancedDocument.builder()
                // Important: You must specify attribute converter providers when you build an EnhancedDocument instance not used with a DynamoDB table.
                .attributeConverterProviders(new CustomAttributeConverterProvider(), DefaultAttributeConverterProvider.create())
                .put("addressDoc", address, Address.class)
                .build();

        // Convert address to a low-level item representation.
        final Map<String, AttributeValue> addressAsAttributeMap = addressEnhancedDoc.getMapOfUnknownType("addressDoc");
        logger.info("addressAsAttributeMap: {}", addressAsAttributeMap.toString());

        // Convert address to a JSON string.
        String addressAsJsonString = addressEnhancedDoc.getJson("addressDoc");
        logger.info("addressAsJsonString: {}", addressAsJsonString);
        // Convert addressEnhancedDoc back to an Address instance.
        Address addressConverted =  addressEnhancedDoc.get("addressDoc", Address.class);
        logger.info("addressConverted: {}", addressConverted.toString());
    }

   /* Console output:
          addressAsAttributeMap: {zipCode=AttributeValue(S=00000), state=AttributeValue(S=my state), street=AttributeValue(S=my street), city=AttributeValue(S=my city)}
          addressAsJsonString: {"zipCode":"00000","state":"my state","street":"my street","city":"my city"}
          addressConverted: Address{street='my street', city='my city', state='my state', zipCode='00000'}
   */
```

**nota**  
Cuando utilice un documento mejorado independiente de una tabla de DynamoDB, asegúrese de establecer explícitamente los proveedores de convertidores de atributos en el generador.  
Por el contrario, el esquema de la tabla de documentos proporciona los proveedores de conversión cuando se utiliza un documento mejorado con una tabla de DynamoDB.

# Uso de extensiones para personalizar operaciones de DynamoDB Enhanced Client
<a name="ddb-en-client-extensions"></a>

La API del cliente mejorado de DynamoDB admite extensiones de complementos que proporcionan funciones que van más allá de las operaciones de mapeo. Las extensiones utilizan dos métodos de enlace para modificar datos durante las operaciones de lectura y escritura:
+ `beforeWrite()`: modifica una operación de escritura antes de que se produzca
+ `afterRead()`: modifica los resultados de una operación de lectura después de que se produzca

Algunas operaciones (como las actualizaciones de elementos) realizan una escritura y luego una lectura, por lo que se llama a ambos métodos de enlace.

## Cómo se cargan las extensiones
<a name="ddb-en-client-extensions-loading"></a>

Las extensiones se cargan en el orden que especifique en el compilador del cliente mejorado. El orden de carga puede ser importante porque una extensión puede actuar sobre valores que han sido transformados por una extensión anterior.

De forma predeterminada, el cliente mejorado carga dos extensiones:
+ `[VersionedRecordExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.html)`: proporciona bloqueo positivo
+ `[AtomicCounterExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/AtomicCounterExtension.html)`: incrementa automáticamente los atributos del contador

Puede anular el comportamiento predeterminado con el compilador del cliente mejorado y cargar cualquier extensión. También puede no especificar ninguna si no desea utilizar las extensiones predeterminadas.

**importante**  
Si carga sus propias extensiones, el cliente mejorado no carga ninguna extensión predeterminada. Si desea el comportamiento proporcionado por alguna de las extensiones predeterminadas, deberá añadirla explícitamente a la lista de extensiones.

El siguiente ejemplo muestra cómo cargar una extensión personalizada llamada `verifyChecksumExtension` después de la `VersionedRecordExtension`. La `AtomicCounterExtension` no se carga en este ejemplo.

```
DynamoDbEnhancedClientExtension versionedRecordExtension = VersionedRecordExtension.builder().build();

DynamoDbEnhancedClient enhancedClient = 
    DynamoDbEnhancedClient.builder()
                          .dynamoDbClient(dynamoDbClient)
                          .extensions(versionedRecordExtension, verifyChecksumExtension)
                          .build();
```

## Detalles y configuración de extensión disponibles
<a name="ddb-en-client-extensions-details"></a>

En las secciones siguientes se proporciona información detallada sobre cada extensión disponible en el SDK.

### Implemente bloqueo positivo con `VersionedRecordExtension`
<a name="ddb-en-client-extensions-VRE"></a>

La extensión `VersionedRecordExtension` proporciona bloqueo positivo incrementando y rastreando el número de versión de los elemento a medida que estos se escriben en la base de datos. Se añade una condición a cada escritura que hará que esta falle si el número de versión del elemento persistido real no coincide con el valor que la aplicación leyó por última vez.

#### Configuración
<a name="ddb-en-client-extensions-VRE-conf"></a>

Para especificar qué atributo usar para rastrear el número de versión del elemento, etiquete un atributo numérico en el esquema de la tabla.

El siguiente fragmento especifica que el atributo `version` debe contener el número de versión del artículo.

```
    @DynamoDbVersionAttribute
    public Integer getVersion() {...};
    public void setVersion(Integer version) {...};
```

El enfoque equivalente del esquema de tabla estático se muestra en el siguiente fragmento.

```
    .addAttribute(Integer.class, a -> a.name("version")
                                       .getter(Customer::getVersion)
                                       .setter(Customer::setVersion)
                                        // Apply the 'version' tag to the attribute.
                                       .tags(VersionedRecordExtension.AttributeTags.versionAttribute())
```

#### Funcionamiento
<a name="ddb-en-client-extensions-VRE-how-it-works"></a>

El bloqueo positivo con la `VersionedRecordExtension` tiene el siguiente efecto en estos métodos `DynamoDbEnhancedClient` y `DynamoDbTable`.

**`putItem`**  
A los nuevos elementos se les asigna un valor de versión inicial de 0. Esto se puede configurar con `@DynamoDbVersionAttribute(startAt = X)`.

**`updateItem`**  
Si recupera un elemento, actualiza una o varias de sus propiedades e intenta guardar los cambios, la operación solamente se lleva a cabo si el número de versión del lado del cliente coincide con el número de versión del lado del servidor.  
Si la operación es correcta, el número de versión aumenta automáticamente en 1. Esto se puede configurar con `@DynamoDbVersionAttribute(incrementBy = X)`.

**`deleteItem`**  
La anotación `DynamoDbVersionAttribute` no tiene ningún efecto. Debe añadir una expresión de condición manualmente al eliminar un elemento.  
En el siguiente ejemplo, se agrega una expresión condicional para garantizar que el elemento eliminado es el elemento que se ha leído. En el siguiente ejemplo, `recordVersion` es el atributo del bean anotado con `@DynamoDbVersionAttribute`.  

```
// 1. Read the item and get its current version.
Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());
// `recordVersion` is the bean's attribute that is annotated with `@DynamoDbVersionAttribute`.
AttributeValue currentVersion = item.getRecordVersion();

// 2. Create conditional delete with the `currentVersion` value.
DeleteItemEnhancedRequest deleteItemRequest =
    DeleteItemEnhancedRequest.builder()
       .key(KEY)
       .conditionExpression(Expression.builder()
           .expression("recordVersion = :current_version_value")
           .putExpressionValue(":current_version_value", currentVersion)
           .build()).build();

customerTable.deleteItem(deleteItemRequest);
```

**`transactWriteItems`**  
+ `addPutItem`: este método tiene el mismo comportamiento que `putItem`.
+ `addUpdateItem`: este método tiene el mismo comportamiento que `updateItem`.
+ `addDeleteItem`: este método tiene el mismo comportamiento que `deleteItem`.

**`batchWriteItem`**  
+ `addPutItem`: este método tiene el mismo comportamiento que `putItem`.
+ `addDeleteItem`: este método tiene el mismo comportamiento que `deleteItem`.

**nota**  
Las tablas globales de DynamoDB usan una [conciliación de “el último escritor gana”](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes) entre actualizaciones simultáneas. DynamoDB hace todo lo posible para determinar quién realizó la última escritura. Si utiliza tablas globales, esta política de “el último escritor gana” significa que las estrategias de bloqueo pueden no funcionar como se esperaba, ya que todas las réplicas acabarán convergiendo en función de la última escritura determinada por DynamoDB. 

#### Cómo deshabilitar
<a name="ddb-en-client-extensions-VRE-how-to-disable"></a>

Para deshabilitar el bloqueo positivo, no utilice la anotación `@DynamoDbVersionAttribute`.

### Implemente contadores con la `AtomicCounterExtension`
<a name="ddb-en-client-extensions-ACE"></a>

La extensión `AtomicCounterExtension` incrementa un atributo numérico etiquetado cada vez que se escribe un registro en la base de datos. Se pueden especificar los valores de inicio y de incremento. Si no se especifica ningún valor, el valor inicial se establece en 0 y el valor del atributo se incrementa en 1.

#### Configuración
<a name="ddb-en-client-extensions-ACE-conf"></a>

Para especificar qué atributo es un contador, etiquete un atributo de tipo `Long` en el esquema de la tabla.

En el siguiente fragmento se muestra el uso de los valores de inicio e incremento predeterminados para el atributo `counter`.

```
    @DynamoDbAtomicCounter
    public Long getCounter() {...};
    public void setCounter(Long counter) {...};
```

El enfoque del esquema de tabla estático se muestra en el siguiente fragmento. La extensión del contador atómico utiliza un valor inicial de 10 e incrementa el valor en 5 cada vez que se escribe el registro.

```
    .addAttribute(Integer.class, a -> a.name("counter")
                                       .getter(Customer::getCounter)
                                       .setter(Customer::setCounter)
                                        // Apply the 'atomicCounter' tag to the attribute with start and increment values.
                                       .tags(StaticAttributeTags.atomicCounter(10L, 5L))
```

### Agregue marcas de tiempo con la `AutoGeneratedTimestampRecordExtension`.
<a name="ddb-en-client-extensions-AGTE"></a>

La extensión `AutoGeneratedTimestampRecordExtension` actualiza automáticamente los atributos de tipo `[Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html)` con una marca de tiempo actual cada vez que el elemento se escribe correctamente en la base de datos. Esta extensión no se carga de forma predeterminada.

#### Configuración
<a name="ddb-en-client-extensions-AGTE-conf"></a>

Para especificar qué atributo actualizar con la marca de tiempo actual, etiquete el atributo `Instant` en el esquema de la tabla.

El atributo `lastUpdate` es el objetivo del comportamiento de la extensión en el siguiente fragmento. Tenga en cuenta el requisito de que el atributo debe ser de tipo `Instant`.

```
    @DynamoDbAutoGeneratedTimestampAttribute
    public Instant getLastUpdate() {...}
    public void setLastUpdate(Instant lastUpdate) {...}
```

El enfoque equivalente del esquema de tabla estático se muestra en el siguiente fragmento.

```
     .addAttribute(Instant.class, a -> a.name("lastUpdate")
                                        .getter(Customer::getLastUpdate)
                                        .setter(Customer::setLastUpdate)
                                        // Applying the 'autoGeneratedTimestamp' tag to the attribute.
                                        .tags(AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute())
```

### Genere un UUID con AutoGeneratedUuidExtension
<a name="ddb-en-client-extensions-AGUE"></a>

La extensión `AutoGeneratedUuidExtension` genera un UUID (identificador único universal) único para un atributo cuando se escribe un registro nuevo en la base de datos. Utiliza el método [UUID.randomUUID()](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#randomUUID--) de Java JDK y lo aplica a los atributos de tipo `java.lang.String`. Esta extensión no se carga de forma predeterminada.

#### Configuración
<a name="ddb-en-client-extensions-AGUE-conf"></a>

El atributo `uniqueId` es el objetivo del comportamiento de la extensión en el siguiente fragmento.

```
    @AutoGeneratedUuidExtension
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

El enfoque equivalente del esquema de tabla estático se muestra en el siguiente fragmento.

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
```

Si quiere que la extensión rellene el UUID solo para los métodos `putItem` y no para los métodos `updateItem`, añade la anotación [comportamiento de actualización](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html) como se muestra en el siguiente fragmento.

```
    @AutoGeneratedUuidExtension
    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

Si utiliza el método de esquema de tabla estática, use el siguiente código equivalente.

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(),
                                              StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))
```

# Ejemplo de extensión personalizada
<a name="ddb-en-client-extensions-custom"></a>

Puede crear extensiones personalizadas implementando la interfaz `DynamoDbEnhancedClientExtension`. La siguiente clase de extensión personalizada muestra un método `beforeWrite()` que usa una expresión de actualización para establecer un atributo `registrationDate` si el elemento de la base de datos aún no tiene uno.

```
public final class CustomExtension implements DynamoDbEnhancedClientExtension {

    // 1. In a custom extension, use an UpdateExpression to define what action to take before
    //    an item is updated.
    @Override
    public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
        if ( context.operationContext().tableName().equals("Customer")
                && context.operationName().equals(OperationName.UPDATE_ITEM)) {
            return WriteModification.builder()
                    .updateExpression(createUpdateExpression())
                    .build();
        }
        return WriteModification.builder().build();  // Return an "empty" WriteModification instance if the extension should not be applied.
                                                     // In this case, if the code is not updating an item on the Customer table.
    }

    private static UpdateExpression createUpdateExpression() {

        // 2. Use a SetAction, a subclass of UpdateAction, to provide the values in the update.
        SetAction setAction =
                SetAction.builder()
                        .path("registrationDate")
                        .value("if_not_exists(registrationDate, :regValue)")
                        .putExpressionValue(":regValue", AttributeValue.fromS(Instant.now().toString()))
                        .build();
        // 3. Build the UpdateExpression with one or more UpdateAction.
        return UpdateExpression.builder()
                .addAction(setAction)
                .build();
    }
}
```

# Utilizar la API de cliente mejorado de DynamoDB de forma asíncrona
<a name="ddb-en-client-async"></a>

Si la aplicación requiere llamadas asíncronas y sin bloqueo a DynamoDB, puede usar [DynamoDBEnhancedAsyncClient.](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html) Es similar a la implementación síncrona, pero con las siguientes diferencias clave:

1. Al compilar `DynamoDbEnhancedAsyncClient`, debe proporcionar la versión asíncrona del cliente estándar, `DynamoDbAsyncClient`, como se muestra en el siguiente fragmento.

   ```
    DynamoDbEnhancedAsyncClient enhancedClient = 
        DynamoDbEnhancedAsyncClient.builder()
                                   .dynamoDbClient(dynamoDbAsyncClient)
                                   .build();
   ```

1. Los métodos que devuelven un único objeto de datos devuelven un `CompletableFuture` del resultado en lugar de solo el resultado. De este modo, la aplicación podrá hacer otras tareas sin bloquear el resultado. En el siguiente fragmento se muestra el método `getItem()` asíncrono. 

   ```
   CompletableFuture<Customer> result = customerDynamoDbTable.getItem(customer);
   // Perform other work here.
   return result.join();   // Now block and wait for the result.
   ```

1. Los métodos que devuelven listas paginadas de resultados devuelven un [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html), en lugar de un [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) que el método sincrónico `DynamoDbEnhanceClient` devuelve para los mismos métodos. A continuación, su aplicación puede suscribir un controlador a ese publicador para tratar los resultados de forma asíncrona sin tener que bloquearse.

   ```
   PagePublisher<Customer> results = customerDynamoDbTable.query(r -> r.queryConditional(keyEqualTo(k -> k.partitionValue("Smith"))));
   results.subscribe(myCustomerResultsProcessor);
   // Perform other work and let the processor handle the results asynchronously.
   ```

   Para ver un ejemplo más completo de cómo trabajar con el `SdkPublisher API`, consulte el [ejemplo](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-scan-async) de la sección que trata sobre el método `scan()` asíncrono de esta guía.

# Anotaciones de clases de datos
<a name="ddb-en-client-anno-index"></a>

En la tabla siguiente se enumeran las anotaciones que se pueden usar en las clases de datos y se proporcionan enlaces a información y ejemplos en esta guía. La tabla está ordenada alfabéticamente en orden ascendente por nombre de anotación.


**Anotaciones de clases de datos utilizadas en esta guía**  

| Nombre de la anotación | La anotación se aplica a 1 | ¿Qué hace? | Dónde aparece en esta guía | 
| --- | --- | --- | --- | 
| DynamoDbAtomicCounter | atributo 2 | Incrementa un atributo numérico etiquetado cada vez que se escribe un registro en la base de datos. | [Introducción y discusión.](ddb-en-client-extensions.md#ddb-en-client-extensions-ACE) | 
| DynamoDbAttribute | atributo | Define o cambia el nombre de una propiedad de bean que está asignada a un atributo de tabla de DynamoDB. |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbAutoGeneratedTimestampAttribute | atributo | Actualiza un atributo etiquetado con una marca de tiempo actual cada vez que el elemento se escribe correctamente en la base de datos. | [Introducción y discusión](ddb-en-client-extensions.md#ddb-en-client-extensions-AGTE). | 
| DynamoDbAutoGeneratedUuid | atributo | Genere un UUID (identificador único universal) único para un atributo cuando se escriba un registro nuevo en la base de datos. | [Introducción y discusión.](ddb-en-client-extensions.md#ddb-en-client-extensions-AGUE) | 
| DynamoDbBean | class | Marca una clase de datos como asignable a un esquema de tabla. | Primer uso en la [clase Customer](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) en la sección Comenzar. A lo largo de la guía aparecen varios usos. | 
| DynamoDbConvertedBy | atributo | Asocia un AttributeConverter personalizado al atributo anotado. | [Discusión inicial y ejemplo.](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-single) | 
| DynamoDbFlatten | atributo | Aplana todos los atributos de una clase de datos de DynamoDB independiente y los agrega como atributos de nivel superior al registro que se lee y escribe en la base de datos.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDBIgnore | atributo |  Hace que el atributo quede sin asignar.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnoreNulls | atributo | Impide guardar los atributos nulos de los objetos anidados de DynamoDB. | [Discusión y ejemplos.](ddb-en-client-adv-features-ignore-null.md) | 
| DynamoDbImmutable | class |  Marca una clase de datos inmutable como asignable a un esquema de tabla.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPartitionKey | atributo |  Marca un atributo como clave de partición principal (clave hash) de la tabla de DynamoDB.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPreserveEmptyObject | atributo |  Especifica que, si no hay datos presentes para el objeto asignado al atributo anotado, el objeto debe inicializarse con todos los campos nulos.  | [Discusión y ejemplos.](ddb-en-client-adv-features-empty.md) | 
| DynamoDbSecondaryPartitionKey | atributo |  Marca un atributo como clave de partición para un índice secundario global.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSecondarySortKey | atributo |  Marca un atributo como clave de clasificación opcional para un índice secundario global o local.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSortKey | atributo |  Marca un atributo como clave de clasificación principal opcional (clave de rango).  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/es_es/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbUpdateBehavior | atributo |  Especifica el comportamiento cuando este atributo se actualiza como parte de una operación de «actualización», como UpdateItem.  | [Introducción y ejemplo.](ddb-en-client-adv-features-upd-behavior.md) | 
| DynamoDbVersionAttribute | atributo | Incrementa el número de versión de un artículo. | [Introducción y discusión.](ddb-en-client-extensions.md#ddb-en-client-extensions-VRE) | 

1Puede aplicar anotaciones de nivel de atributo al getter o setter, pero no a ambos. Esta guía muestra las anotaciones en getters

2El término `property` se utiliza normalmente para un valor encapsulado en una clase de datos JavaBean. Sin embargo, en esta guía se utiliza el término `attribute` en su lugar para mantener la coherencia con la terminología utilizada por DynamoDB.