

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.

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