

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# Gunakan fitur pemetaan tingkat lanjut
<a name="ddb-en-client-adv-features"></a>

Pelajari tentang fitur skema tabel lanjutan di DynamoDB Enhanced Client API.

## Memahami jenis skema tabel
<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)`adalah antarmuka ke fungsionalitas pemetaan DynamoDB Enhanced Client API. Hal ini dapat memetakan objek data ke dan dari peta [AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html). Sebuah `TableSchema` objek perlu tahu tentang struktur tabel yang dipetakannya. Informasi struktur ini disimpan dalam suatu [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)objek.

API klien yang disempurnakan memiliki beberapa implementasi`TableSchema`, yang mengikuti. 

### Skema tabel yang dihasilkan dari kelas beranotasi
<a name="ddb-en-client-adv-features-schema-mapped"></a>

Ini adalah operasi yang cukup mahal untuk membangun `TableSchema` dari kelas beranotasi, jadi kami sarankan melakukan ini sekali, saat startup aplikasi.

 [ BeanTableSchema ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.html)   
Implementasi ini dibangun berdasarkan atribut dan anotasi kelas kacang. Contoh pendekatan ini ditunjukkan di [bagian Memulai](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean).  
Jika a `BeanTableSchema` tidak berperilaku seperti yang Anda harapkan, aktifkan pencatatan debug untuk. `software.amazon.awssdk.enhanced.dynamodb.beans`

[ImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/ImmutableTableSchema.html)  
Implementasi ini dibangun dari kelas data yang tidak dapat diubah. Pendekatan ini dijelaskan di [Bekerja dengan kelas data yang tidak dapat diubah](ddb-en-client-use-immut.md) bagian ini.

### Skema tabel yang dihasilkan dengan pembangun
<a name="ddb-en-client-adv-features-schema-static"></a>

Berikut `TableSchema` s dibangun dari kode dengan menggunakan pembangun. Pendekatan ini lebih murah daripada pendekatan yang menggunakan kelas data beranotasi. Pendekatan builder menghindari penggunaan anotasi dan tidak memerlukan standar JavaBean penamaan.

[StaticTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.html)  
Implementasi ini dibangun untuk kelas data yang bisa berubah. Bagian memulai dari panduan ini menunjukkan cara [membuat `StaticTableSchema` menggunakan pembangun](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)  
Demikian pula dengan cara Anda membangun`StaticTableSchema`, Anda menghasilkan implementasi jenis ini `TableSchema` menggunakan [pembangun](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html) untuk digunakan dengan kelas data yang tidak dapat diubah.

### Skema tabel untuk data tanpa skema tetap
<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)  
Tidak seperti implementasi lain dari`TableSchema`, Anda tidak mendefinisikan atribut untuk sebuah `DocumentTableSchema` instance. Biasanya, Anda hanya menentukan kunci utama dan penyedia konverter atribut. Sebuah `EnhancedDocument` instance menyediakan atribut yang Anda bangun dari elemen individual atau dari string JSON.

# Sertakan atau kecualikan atribut secara eksplisit
<a name="ddb-en-client-adv-features-inex-attr"></a>

DynamoDB Enhanced Client API menawarkan anotasi untuk mengecualikan atribut kelas data agar tidak menjadi atribut pada tabel. Dengan API, Anda juga dapat menggunakan nama atribut yang berbeda dari nama atribut kelas data.

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

Untuk mengabaikan atribut yang seharusnya tidak dipetakan ke tabel DynamoDB, tandai atribut dengan anotasi. `@DynamoDbIgnore`

```
private String internalKey;

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

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

Untuk mengubah nama atribut yang digunakan dalam tabel DynamoDB, tandai dengan anotasi dan berikan nama `@DynamoDbAttribute` yang berbeda.

```
private String internalKey;

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

# Konversi atribut kontrol
<a name="ddb-en-client-adv-features-conversion"></a>

Secara default, skema tabel menyediakan konverter untuk banyak jenis Java umum melalui implementasi default antarmuka. `[AttributeConverterProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.html)` Anda dapat mengubah keseluruhan perilaku default dengan `AttributeConverterProvider` implementasi kustom. Anda juga dapat mengubah konverter untuk satu atribut.

Untuk daftar konverter yang tersedia, lihat [AttributeConverter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html)antarmuka Java doc.

## Menyediakan penyedia konverter atribut khusus
<a name="ddb-en-client-adv-features-conversion-prov"></a>

Anda dapat memberikan satu `AttributeConverterProvider` atau satu rantai `AttributeConverterProvider` s yang dipesan melalui `@DynamoDbBean` `(converterProviders = {…})` anotasi. Setiap kustom `AttributeConverterProvider` harus memperluas `AttributeConverterProvider` antarmuka.

Perhatikan bahwa jika Anda menyediakan rantai penyedia konverter atribut Anda sendiri, Anda akan mengganti penyedia konverter default,`DefaultAttributeConverterProvider`. Jika Anda ingin menggunakan fungsionalitas`DefaultAttributeConverterProvider`, Anda harus memasukkannya ke dalam rantai. 

Dimungkinkan juga untuk membubuhi keterangan kacang dengan array kosong. `{}` Ini menonaktifkan penggunaan penyedia konverter atribut apa pun, termasuk default. Dalam hal ini semua atribut yang akan dipetakan harus memiliki konverter atribut mereka sendiri.

Cuplikan berikut menunjukkan penyedia konverter tunggal.

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

}
```

Cuplikan berikut menunjukkan penggunaan rantai penyedia konverter. Karena default SDK disediakan terakhir, ia memiliki prioritas terendah.

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

}
```

Pembangun skema tabel statis memiliki `attributeConverterProviders()` metode yang bekerja dengan cara yang sama. Ini ditunjukkan dalam cuplikan berikut.

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

## Ganti pemetaan atribut tunggal
<a name="ddb-en-client-adv-features-conversion-single"></a>

Untuk mengganti cara atribut tunggal dipetakan, berikan atribut `AttributeConverter` untuk atribut. Penambahan ini mengesampingkan konverter apa pun yang disediakan oleh skema `AttributeConverterProviders` tabel. Ini menambahkan konverter khusus hanya untuk atribut itu. Atribut lain, bahkan yang dari tipe yang sama, tidak akan menggunakan konverter itu kecuali jika secara eksplisit ditentukan untuk atribut lainnya.

`@DynamoDbConvertedBy`Anotasi ini digunakan untuk menentukan `AttributeConverter` kelas kustom seperti yang ditunjukkan dalam cuplikan berikut.

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

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

Pembangun untuk skema statis memiliki `attributeConverter()` metode pembangun atribut yang setara. Metode ini mengambil contoh dari `AttributeConverter` sebagai berikut menunjukkan.

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

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

Contoh ini menunjukkan `AttributeConverterProvider` implementasi yang menyediakan konverter atribut untuk [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)objek. 

`SimpleUser`Kelas berikut berisi atribut bernama `lastUsedCookie` yang merupakan instance dari`HttpCookie`.

Parameter untuk `@DynamoDbBean` anotasi mencantumkan dua `AttributeConverterProvider` kelas yang menyediakan konverter.

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

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

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

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

        public HttpCookie getLastUsedCookie() {
            return lastUsedCookie;
        }

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

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

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

------

`CookieConverterProvider`Dalam contoh berikut memberikan contoh dari sebuah`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);
        }
    }
```

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

Dalam `transformFrom()` metode `HttpCookieConverter` kelas berikut, kode menerima `HttpCookie` instance dan mengubahnya menjadi peta DynamoDB yang disimpan sebagai atribut.

`transformTo()`Metode menerima parameter peta DynamoDB, kemudian memanggil konstruktor `HttpCookie` yang membutuhkan nama dan nilai.

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

# Ubah perilaku pembaruan atribut
<a name="ddb-en-client-adv-features-upd-behavior"></a>

Anda dapat menyesuaikan perilaku pembaruan atribut individual saat Anda melakukan operasi *pembaruan*. [Beberapa contoh operasi pembaruan di DynamoDB Enhanced Client API [adalah updateItem](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#updateItem(T)) () dan (). transactWriteItems](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))

Misalnya, bayangkan Anda ingin menyimpan stempel waktu yang *dibuat pada* catatan Anda. Namun, Anda ingin nilainya ditulis hanya jika tidak ada nilai yang ada untuk atribut yang sudah ada dalam database. Dalam hal ini, Anda menggunakan perilaku `[WRITE\$1IF\$1NOT\$1EXISTS](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html#WRITE_IF_NOT_EXISTS)` pembaruan.

Contoh berikut menunjukkan anotasi yang menambahkan perilaku ke `createdOn` atribut.

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

Anda dapat mendeklarasikan perilaku pembaruan yang sama ketika Anda membangun skema tabel statis seperti yang ditunjukkan pada contoh berikut setelah baris komentar 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();
```

# Ratakan atribut dari kelas lain
<a name="ddb-en-client-adv-features-flatmap"></a>

Jika atribut untuk tabel Anda tersebar di beberapa kelas Java yang berbeda, baik melalui pewarisan atau komposisi, DynamoDB Enhanced Client API menyediakan dukungan untuk meratakan atribut menjadi satu kelas.

## Gunakan warisan
<a name="ddb-en-client-adv-features-flatmap-inheritance"></a>

Jika kelas Anda menggunakan pewarisan, gunakan pendekatan berikut untuk meratakan hierarki.

### Gunakan kacang beranotasi
<a name="ddb-en-client-adv-features-flatmap-inheritance-anno"></a>

Untuk pendekatan anotasi, kedua kelas harus membawa `@DynamoDbBean` anotasi dan kelas harus membawa satu atau lebih anotasi kunci primer.

Berikut ini menunjukkan contoh kelas data yang memiliki hubungan warisan.

------
#### [ 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 ]

[`onMethod`Opsi](https://projectlombok.org/features/experimental/onX) Lombok menyalin anotasi DynamoDB berbasis atribut, seperti, ke kode yang dihasilkan. `@DynamoDbPartitionKey`

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

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

------

### Gunakan skema statis
<a name="ddb-en-client-adv-features-flatmap-inheritance-static"></a>

Untuk pendekatan skema statis, gunakan `extend()` metode pembangun untuk menciutkan atribut kelas induk ke kelas anak. Ini ditampilkan setelah komentar baris 1 dalam contoh berikut.

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

Contoh skema statis sebelumnya menggunakan kelas data berikut. Karena pemetaan didefinisikan ketika Anda membangun skema tabel statis, kelas data tidak memerlukan anotasi.

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

------

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

Jika kelas Anda menggunakan komposisi, gunakan pendekatan berikut untuk meratakan hierarki.

### Gunakan kacang beranotasi
<a name="ddb-en-client-adv-features-flatmap-comp-anno"></a>

`@DynamoDbFlatten`Anotasi meratakan kelas yang terkandung.

Contoh kelas data berikut menggunakan `@DynamoDbFlatten` anotasi untuk secara efektif menambahkan semua atribut kelas yang terkandung ke `GenericRecord` `Customer` kelas.

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

------

Anda dapat menggunakan anotasi rata untuk meratakan sebanyak mungkin kelas yang memenuhi syarat yang Anda butuhkan. Batasan berikut berlaku:
+ Semua nama atribut harus unik setelah diratakan.
+ Tidak boleh ada lebih dari satu kunci partisi, kunci sortir, atau nama tabel.

### Gunakan skema statis
<a name="ddb-en-client-adv-features-flatmap-comp-static"></a>

Saat Anda membuat skema tabel statis, gunakan `flatten()` metode pembangun. Anda juga menyediakan metode pengambil dan penyetel yang mengidentifikasi kelas yang terkandung.

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

Contoh skema statis sebelumnya menggunakan kelas data berikut.

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

------

Anda dapat menggunakan pola pembangun untuk meratakan sebanyak mungkin kelas yang memenuhi syarat yang Anda butuhkan.

## Implikasi untuk kode lain
<a name="ddb-en-client-adv-features-flatmap-compare"></a>

Bila Anda menggunakan `@DynamoDbFlatten` atribut (atau metode `flatten()` builder), item di DynamoDB berisi atribut untuk setiap atribut dari objek yang disusun. Ini juga mencakup atribut dari objek penulisan. 

Sebaliknya, jika Anda membuat anotasi kelas data dengan kelas tersusun dan tidak menggunakan`@DynamoDbFlatten`, item disimpan dengan objek tersusun sebagai atribut tunggal.

Misalnya, bandingkan `Customer` kelas yang ditunjukkan dalam [perataan dengan contoh komposisi dengan](#ddb-en-client-adv-features-flatmap-comp-anno) dan tanpa perataan atribut. `record` Anda dapat memvisualisasikan perbedaannya dengan JSON seperti yang ditunjukkan pada tabel berikut.


****  

| Dengan perataan | Tanpa perataan | 
| --- | --- | 
| 3 atribut | 2 atribut | 
|  <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>  | 

Perbedaannya menjadi penting jika Anda memiliki kode lain yang mengakses tabel DynamoDB yang mengharapkan untuk menemukan atribut tertentu.

# Bekerja dengan atribut yang kacang, peta, daftar, dan set
<a name="ddb-en-client-adv-features-nested"></a>

Definisi kacang, seperti `Person` kelas yang ditunjukkan di bawah ini, mungkin mendefinisikan properti (atau atribut) yang merujuk ke tipe dengan atribut tambahan. Misalnya, di `Person` kelas, `mainAddress` adalah properti yang mengacu pada `Address` kacang yang mendefinisikan atribut nilai tambahan. `addresses`mengacu pada Peta Jawa, yang unsur-unsurnya mengacu pada `Address` kacang. Tipe kompleks ini dapat dianggap sebagai wadah atribut sederhana yang Anda gunakan untuk nilai datanya dalam konteks DynamoDB. 

*DynamoDB mengacu pada properti nilai elemen bersarang, seperti peta, daftar, atau kacang, sebagai atribut bersarang.* *Panduan [Pengembang Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes) mengacu pada bentuk yang disimpan dari peta Java, daftar atau kacang sebagai jenis dokumen.* Atribut sederhana yang Anda gunakan untuk nilai data mereka di Java disebut sebagai *tipe skalar* di DynamoDB. Set, yang berisi beberapa elemen skalar dari jenis yang sama, dan disebut sebagai *tipe set*. 

Penting untuk diketahui bahwa DynamoDB Enhanced Client API mengonversi properti yang bean ke jenis dokumen peta DynamoDB saat disimpan.

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

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

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

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

    public String getFirstName() {
        return firstName;
    }

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

    public String getLastName() {
        return lastName;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Address getMainAddress() {
        return mainAddress;
    }

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

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

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

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

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

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

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

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

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

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

    public Address() {
    }

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

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

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

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

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

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

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

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

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

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

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

## `PhoneNumber` kelas
<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 + '\'' +
                '}';
    }
}
```

## Simpan tipe kompleks
<a name="ddb-en-client-adv-features-nested-mapping"></a>

### Gunakan kelas data beranotasi
<a name="ddb-en-client-adv-features-nested-map-anno"></a>

Anda menyimpan atribut bersarang untuk kelas khusus hanya dengan membuat anotasi. `Address`Kelas dan `PhoneNumber` kelas yang ditampilkan sebelumnya dianotasi hanya dengan anotasi. `@DynamoDbBean` Saat DynamoDB Enhanced Client API membangun skema tabel untuk `Person` kelas dengan cuplikan berikut, API akan menemukan penggunaan class `PhoneNumber` dan dan membangun pemetaan yang sesuai agar berfungsi dengan DynamoDB. `Address`

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

### Gunakan skema abstrak dengan pembangun
<a name="ddb-en-client-adv-features-nested-map-builder"></a>

Pendekatan alternatifnya adalah dengan menggunakan pembangun skema tabel statis untuk setiap kelas kacang bersarang seperti yang ditunjukkan pada kode berikut.

Skema tabel untuk `PhoneNumber` kelas `Address` dan abstrak dalam arti bahwa mereka tidak dapat digunakan dengan tabel DynamoDB. Ini karena mereka tidak memiliki definisi untuk kunci utama. Mereka digunakan, bagaimanapun, sebagai skema bersarang dalam skema tabel untuk kelas. `Person`

Setelah baris komentar 1 dan 2 dalam definisi`PERSON_TABLE_SCHEMA`, Anda melihat kode yang menggunakan skema tabel abstrak. Penggunaan `documentOf` dalam `EnhanceType.documentOf(...)` metode tidak menunjukkan bahwa metode mengembalikan `EnhancedDocument` jenis API Dokumen yang Ditingkatkan. `documentOf(...)`Metode dalam konteks ini mengembalikan objek yang tahu bagaimana memetakan argumen kelasnya ke dan dari DynamoDB atribut tabel dengan menggunakan argumen skema tabel.

#### Kode skema statis
<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();
```

## Atribut proyek dari tipe kompleks
<a name="ddb-en-client-adv-features-nested-projection"></a>

Untuk `query()` dan `scan()` metode, Anda dapat menentukan atribut mana yang ingin Anda kembalikan dalam hasil dengan menggunakan panggilan metode seperti `addNestedAttributeToProject()` dan`attributesToProject()`. DynamoDB Enhanced Client API mengubah parameter panggilan metode Java [menjadi ekspresi proyeksi sebelum permintaan dikirim](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html).

Contoh berikut mengisi `Person` tabel dengan dua item, kemudian melakukan tiga operasi pemindaian. 

Pemindaian pertama mengakses semua item dalam tabel untuk membandingkan hasilnya dengan operasi pemindaian lainnya. 

Pemindaian kedua menggunakan metode [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))builder untuk mengembalikan hanya nilai `street` atribut.

Operasi pemindaian ketiga menggunakan metode [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...))builder untuk mengembalikan data untuk atribut tingkat pertama,`hobbies`. Jenis atribut `hobbies` adalah daftar. Untuk mengakses item daftar individual, lakukan `get()` operasi pada daftar.

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

**catatan**  
Jika `attributesToProject()` metode mengikuti metode pembangun lain yang menambahkan atribut yang ingin Anda proyeksikan, daftar nama atribut yang diberikan ke `attributesToProject()` menggantikan semua nama atribut lainnya.  
Pemindaian yang dilakukan dengan `ScanEnhancedRequest` instance dalam cuplikan berikut hanya mengembalikan data hobi.  

```
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]}
```
Cuplikan kode berikut menggunakan `attributesToProject()` metode terlebih dahulu. Urutan ini mempertahankan semua atribut lain yang diminta.  

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

## Gunakan tipe kompleks dalam ekspresi
<a name="ddb-en-client-adv-features-nested-expressions"></a>

Anda dapat menggunakan tipe kompleks dalam ekspresi, seperti ekspresi filter dan ekspresi kondisi, dengan menggunakan operator dereferencing untuk menavigasi struktur tipe kompleks. Untuk objek dan peta, gunakan `. (dot)` dan untuk elemen daftar gunakan `[n]` (tanda kurung siku di sekitar nomor urut elemen). Anda tidak dapat merujuk ke elemen individual dari satu set, tetapi Anda dapat menggunakan [`contains`fungsi](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions) tersebut.

Contoh berikut menunjukkan dua ekspresi filter yang digunakan dalam operasi scan. Ekspresi filter menentukan kondisi kecocokan untuk item yang Anda inginkan dalam hasil. Contoh menggunakan`Person`,`Address`, dan `PhoneNumber` kelas yang ditampilkan sebelumnya.

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

### Metode pembantu yang mengisi tabel
<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);
    }
```

### Representasi JSON item dalam database
<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"
  }
 ]
}
```

## Perbarui item yang berisi tipe kompleks
<a name="ddb-en-client-adv-features-nested-updates"></a>

Untuk memperbarui item yang berisi tipe kompleks, Anda memiliki dua pendekatan dasar:
+ Pendekatan 1: Pertama ambil item (dengan menggunakan`getItem`), perbarui objek, lalu panggil`DynamoDbTable#updateItem`.
+ Pendekatan 2: Jangan mengambil item, tetapi buat instance baru, atur properti yang ingin Anda perbarui, dan kirimkan instance `DynamoDbTable#updateItem` dengan menyetel nilai yang sesuai dari. [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) Pendekatan ini tidak mengharuskan Anda mengambil item sebelum memperbaruinya.

Contoh yang ditunjukkan di bagian ini menggunakan`Person`,`Address`, dan `PhoneNumber` kelas yang ditunjukkan sebelumnya.

### Perbarui pendekatan 1: ambil, lalu perbarui
<a name="ddb-en-client-adv-features-nested-updates-retreive"></a>

Dengan menggunakan pendekatan ini, Anda memastikan bahwa tidak ada data yang hilang pada pembaruan. DynamoDB Enhanced Client API membuat ulang bean dengan atribut dari item yang disimpan di DynamoDB termasuk nilai tipe kompleks. Anda kemudian perlu menggunakan getter dan setter untuk memperbarui kacang. Kelemahan dari pendekatan ini adalah biaya yang Anda keluarkan untuk mengambil item terlebih dahulu.

Contoh berikut menunjukkan bahwa tidak ada data yang hilang jika Anda pertama kali mengambil item sebelum memperbaruinya.

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

### Pendekatan pembaruan 2: Gunakan `IgnoreNullsMode` enum tanpa mengambil item terlebih dahulu
<a name="ddb-en-client-adv-features-nested-updates-nullmode"></a>

Untuk memperbarui item di DynamoDB, Anda dapat memberikan objek baru yang hanya memiliki properti yang ingin diperbarui dan membiarkan nilai lainnya sebagai null. Dengan pendekatan ini, Anda perlu mengetahui bagaimana nilai nol dalam objek diperlakukan oleh SDK dan bagaimana Anda dapat mengontrol perilaku.

Untuk menentukan properti bernilai nol mana yang ingin diabaikan oleh SDK, berikan `IgnoreNullsMode` enum saat Anda membuat. [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) Sebagai contoh menggunakan salah satu nilai yang disebutkan, cuplikan berikut menggunakan mode. `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"
    }
  }
*/
```

[Panduan Pengembang Amazon DynamoDB berisi informasi lebih lanjut tentang ekspresi pembaruan.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)

#### Deskripsi opsi `IgnoreNullsMode`
<a name="ignore-nulls-mode-descriptions"></a>
+ `IgnoreNullsMode.SCALAR_ONLY`- Gunakan pengaturan ini untuk memperbarui atribut skalar di tingkat manapun. SDK membuat pernyataan pembaruan yang hanya mengirimkan atribut skalar non-null ke DynamoDB. SDK mengabaikan atribut skalar bernilai nol dari kacang atau peta, mempertahankan nilai yang disimpan di DynamoDB.

  Saat Anda memperbarui atribut skalar map atau bean, peta harus sudah ada di DynamoDB. Jika Anda menambahkan peta atau kacang ke objek yang belum ada untuk objek di DynamoDB, Anda mendapatkan dengan *pesan Jalur dokumen `DynamoDbException` yang disediakan dalam ekspresi pembaruan tidak valid untuk pembaruan*. Anda harus menggunakan `MAPS_ONLY` mode untuk menambahkan kacang atau peta ke DynamoDB sebelum Anda memperbarui salah satu atributnya.
+ `IgnoreNullsMode.MAPS_ONLY`- Gunakan pengaturan ini untuk menambah atau mengganti properti yang merupakan kacang atau peta. SDK menggantikan atau menambahkan peta atau kacang apa pun yang disediakan dalam objek. Setiap kacang atau peta yang null dalam objek diabaikan, mempertahankan peta yang ada di DynamoDB.
+ `IgnoreNullsMode.DEFAULT`- Dengan pengaturan ini, SDK tidak pernah mengabaikan nilai null. Atribut skalar pada tingkat manapun yang null diperbarui ke null. SDK memperbarui properti bean, map, list, atau set bernilai nol dalam objek ke null di DynamoDB. Saat Anda menggunakan mode ini—atau tidak menyediakan mode karena ini adalah mode default—Anda harus mengambil item terlebih dahulu sehingga nilai di DynamoDB tidak disetel ke null yang disediakan dalam objek untuk diperbarui, kecuali niat Anda adalah untuk mengatur nilai ke null.

Dalam semua mode, jika Anda memberikan objek `updateItem` yang memiliki daftar atau set non-null, daftar atau set disimpan ke DynamoDB. 

#### Mengapa mode?
<a name="ddb-en-client-adv-features-nested-updates-nullmodes-why"></a>

Saat Anda memberikan objek dengan kacang atau peta ke `updateItem` metode, SDK tidak dapat mengetahui apakah objek tersebut harus menggunakan nilai properti dalam kacang (atau nilai entri di peta) untuk memperbarui item, atau apakah keseluruhan bean/map harus menggantikan apa yang telah disimpan ke DynamoDB.

Bekerja dari contoh sebelumnya yang menunjukkan pengambilan item terlebih dahulu, mari kita coba memperbarui `city` atribut `mainAddress` tanpa pengambilan.

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

Dua contoh berikut menunjukkan penggunaan nilai `MAPS_ONLY` dan `SCALAR_ONLY` enumerasi. `MAPS_ONLY`menambahkan peta dan `SCALAR_ONLY` memperbarui peta.

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

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

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

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


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

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

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

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

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

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

Lihat tabel berikut untuk melihat nilai null mana yang diabaikan untuk setiap mode. Anda sering dapat bekerja dengan salah satu `SCALAR_ONLY` dan `MAPS_ONLY` kecuali ketika Anda bekerja dengan kacang atau peta.


**Properti bernilai nol mana dalam objek yang dikirimkan yang diabaikan SDK untuk `updateItem` setiap mode?**  

| Jenis properti | dalam mode SCALAR\$1ONLY | dalam mode MAPS\$1ONLY | dalam mode DEFAULT | 
| --- | --- | --- | --- | 
| Skalar teratas | Ya | Ya | Tidak | 
| Kacang atau peta | Ya | Ya | Tidak | 
| Nilai skalar entri kacang atau peta | Ya1 | Tidak 2 | Tidak | 
| Daftar atau set | Ya | Ya | Tidak | 

1 Ini mengasumsikan peta sudah ada di DynamoDB. Nilai skalar apa pun—null atau bukan null—dari kacang atau peta yang Anda berikan di objek untuk pembaruan mengharuskan jalur ke nilai ada di DynamoDB. SDK membuat jalur ke atribut dengan menggunakan operator `. (dot)` dereference sebelum mengirimkan permintaan.

2 Karena Anda menggunakan `MAPS_ONLY` mode untuk sepenuhnya mengganti atau menambahkan kacang atau peta, semua nilai nol dalam kacang atau peta dipertahankan di peta yang disimpan ke DynamoDB.

# Pertahankan benda kosong dengan `@DynamoDbPreserveEmptyObject`
<a name="ddb-en-client-adv-features-empty"></a>

Jika Anda menyimpan kacang ke Amazon DynamoDB dengan objek kosong dan Anda ingin SDK membuat ulang objek kosong saat pengambilan, beri anotasi pengambil kacang bagian dalam dengan. `@DynamoDbPreserveEmptyObject`

Untuk mengilustrasikan cara kerja anotasi, contoh kode menggunakan dua kacang berikut.

## Contoh kacang
<a name="ddb-en-client-adv-features-empty-ex1"></a>

Kelas data berikut berisi dua `InnerBean` bidang. Metode getter,`getInnerBeanWithoutAnno()`, tidak dijelaskan dengan. `@DynamoDbPreserveEmptyObject` `getInnerBeanWithAnno()`Metode ini dijelaskan.

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

Contoh dari `InnerBean` kelas berikut adalah bidang `MyBean` dan diinisialisasi sebagai objek kosong dalam kode contoh.

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

Contoh kode berikut menyimpan `MyBean` objek dengan kacang dalam yang diinisialisasi ke DynamoDB dan kemudian mengambil item tersebut. Output yang dicatat menunjukkan bahwa tidak `innerBeanWithoutAnno` diinisialisasi, tetapi `innerBeanWithAnno` telah dibuat.

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

## Skema statis alternatif
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

Anda dapat menggunakan `StaticTableSchema` versi skema tabel berikut sebagai pengganti anotasi pada kacang.

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

# Hindari menyimpan atribut null dari objek bersarang
<a name="ddb-en-client-adv-features-ignore-null"></a>

Anda dapat melewati atribut null dari objek bersarang saat menyimpan objek kelas data ke DynamoDB dengan menerapkan anotasi. `@DynamoDbIgnoreNulls` Sebaliknya, atribut tingkat atas dengan nilai null tidak pernah disimpan ke database.

Untuk mengilustrasikan cara kerja anotasi, contoh kode menggunakan dua kacang berikut.

## Contoh kacang
<a name="ddb-en-client-adv-features-ignore-null-ex1"></a>

Kelas data berikut berisi dua `InnerBean` bidang. Metode getter,`getInnerBeanWithoutAnno()`, tidak dijelaskan. `getInnerBeanWithIgnoreNullsAnno()`Metode ini dianotasi dengan. `@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();
    }
}
```

Contoh dari `InnerBean` kelas berikut adalah bidang `MyBean` dan digunakan dalam kode contoh berikut.

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

Contoh kode berikut menciptakan `InnerBean` objek dan menetapkan hanya satu dari dua atribut dengan nilai. 

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

Untuk memvisualisasikan data tingkat rendah yang dikirim ke DynamoDB, kode mencatat peta atribut sebelum menyimpan objek. `MyBean`

Output yang dicatat menunjukkan bahwa `innerBeanWithIgnoreNullsAnno` output satu atribut,

```
innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)})
```

`innerBeanWithoutAnno`Instance menghasilkan dua atribut. Satu atribut memiliki nilai 200 dan yang lainnya adalah atribut bernilai nol.

```
innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})
```

## Representasi JSON dari peta atribut
<a name="ddb-en-client-adv-features-ignore-null-ex2"></a>

Representasi JSON berikut membuatnya lebih mudah untuk melihat data yang disimpan ke DynamoDB.

```
{
  "id": {
    "S": "1"
  },
  "innerBeanWithIgnoreNullsAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      }
    }
  },
  "innerBeanWithoutAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      },
      "innerBeanFieldString": {
        "NULL": true
      }
    }
  }
}
```

## Skema statis alternatif
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

Anda dapat menggunakan `StaticTableSchema` versi berikut dari skema tabel di tempat anotasi kelas data.

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