Bean、Map、List、Set の属性を使用する - AWS SDK for Java 2.x

Bean、Map、List、Set の属性を使用する

以下に示す Person クラスなどの Bean 定義は、追加の属性を持つ型を参照するプロパティ (または属性) を定義する場合があります。例えば、 Person クラスでは、 mainAddress は追加の値属性を定義する Address Bean を参照するプロパティです。addresses は Java Map を参照し、その要素は Address Bean を参照します。これらの複合型は、DynamoDB のコンテキストでデータ値に使用するシンプルな属性のコンテナと考えることができます。

DynamoDB は、マップ、リスト、Bean などのネストされた要素の値プロパティをネストされた属性として参照します。「Amazon DynamoDB デベロッパーガイド」では、Java Map、List、または Bean の保存形式をドキュメントタイプとして参照します。Java のデータ値に使用するシンプルな属性は、DynamoDB ではスカラー型と呼ばれます。Set は同じタイプの複数のスカラー要素を含み、セット型と呼ばれます。

DynamoDB 拡張クライアント API は、保存時に Bean であるプロパティを DynamoDB マップドキュメントタイプに変換することに注意してください。

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

複合型を保存する

注釈付きデータクラスを使用する

カスタムクラスのネストされた属性は、注釈を付けるだけで保存できます。前に示した Address クラスと PhoneNumber クラスには、@DynamoDbBean 注釈のみの注釈が付けられています。DynamoDB Enhanced Client API が次のスニペットを使用して Person クラスのテーブルスキーマを構築すると、API はAddress および PhoneNumber クラスの使用を検出し、DynamoDB と動作する対応するマッピングを構築します。

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

ビルダーで抽象スキーマを使用する

別の方法は、次のコードに示すように、ネストされた各 Bean クラスに静的テーブルスキーマビルダーを使用することです。

Address および PhoneNumber クラスのテーブルスキーマは、DynamoDB テーブルでは使用できないという意味で抽象的です。これは、プライマリキーの定義が不足しているためです。ただし、Person クラスのテーブルスキーマではネストされたスキーマとして使用されます。

PERSON_TABLE_SCHEMA の定義のコメント 1 行目と 2 行目の後に、抽象テーブルスキーマを使用するコードが表示されます。EnhanceType.documentOf(...) メソッド内で documentOf を使用しても、メソッドが拡張ドキュメント API の EnhancedDocument タイプを返すことを示すわけではありません。このコンテキストの documentOf(...) メソッドは、テーブルスキーマ引数を使用して DynamoDB テーブル属性との間でクラス引数をマッピングする方法を知っているオブジェクトを返します。

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

複合型のプロジェクト属性

query() および scan() メソッドでは、addNestedAttributeToProject()attributesToProject() などのメソッドの呼び出しを使用して、結果で返される属性を指定できます。DynamoDB 拡張クライアント API は、リクエストが送信される前に Java メソッド呼び出しパラメータをプロジェクション式に変換します。

次の例では、Person テーブルに 2 つの項目を設定し、3 回のスキャンオペレーションを実行します。

1 回目のスキャンでは、結果を他のスキャンオペレーションと比較するために、テーブル内のすべての項目にアクセスします。

2 回目のスキャンでは、addNestedAttributeToProject() ビルダーメソッドを使用して street 属性値のみを返します。

3 回目のスキャンオペレーションでは、attributesToProject() ビルダーメソッドを使用して第 1 レベルの属性のデータ、hobbies を返します。hobbies の属性タイプはリストです。個々のリスト項目にアクセスするには、リストに対して get() オペレーションを実行します。

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
注記

その attributesToProject() メソッドが、プロジェクションする属性を追加する他のビルダーメソッドに続く場合は、attributesToProject() に渡された属性名のリストは他のすべての属性名を置き換えます。

次のスニペット ScanEnhancedRequest のインスタンスで実行されたスキャンでは、ホビーデータのみが返されます。

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

以下のコード例では、最初に attributesToProject() メソッドを使用します。この順序付けでは、要求された他のすべての属性が保持されます。

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

式で複合型を使用する

フィルター式や条件式などの式で複合型を使用するには、参照解除演算子を使用して複合型の構造をたどります。Object と Map の場合は . (dot) を使用し、List 要素の場合は [n] (要素のシーケンス番号を囲む角括弧) を使用します。Set の個々の要素を参照することはできませんが、 contains 関数を使用できます。

次の例は、スキャンオペレーションで使用される 2 つのフィルター式を示しています。フィルター式は、結果に含める項目の一致条件を指定します。次の例では、前に示した PersonAddress、および PhoneNumber クラスを使用しています。

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

複合型を含む項目の更新

複合型を含む項目を更新するには、2 つの基本的なアプローチがあります。

  • アプローチ 1: まず項目を取得し (getItem を使用)、オブジェクトを更新してから、DynamoDbTable#updateItem を呼び出します。

  • アプローチ 2: 項目を取得せずに新しいインスタンスを作成し、更新するプロパティを設定し、適切な値の IgnoreNullsMode を設定してインスタンスを DynamoDbTable#updateItem に送信します。このアプローチでは、更新する前に項目を取得する必要はありません。

このセクションに示す例では、前に示した PersonAddress、および PhoneNumber クラスを使用します。

更新アプローチ 1: 取得してから更新する

このアプローチを使用することで、更新時にデータが失われないようにします。DynamoDB 拡張クライアント API は、DynamoDB に保存された項目の属性を使用して、複合型の値を含めて Bean を再作成します。次に、ゲッターとセッターを使用して Bean を更新する必要があります。このアプローチの欠点は、最初に項目を取得する際に発生するコストです。

次の例は、更新する前に項目を最初に取得してもデータが失われないことを示しています。

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

更新アプローチ 2: 最初に項目を取得せずに IgnoreNullsMode 列挙型を使用する

DynamoDB で項目を更新するには、更新するプロパティのみを持つ新しいオブジェクトを指定し、他の値を null のままにします。このアプローチでは、オブジェクトの null 値が SDK によってどのように処理されるかと、動作を制御する方法に注意する必要があります。

SDK で無視する null 値のプロパティを指定するには、UpdateItemEnhancedRequest を構築するときに IgnoreNullsMode 列挙型を指定します。列挙値のいずれかを使用する例として、次のスニペットは 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" } } */

更新式の詳細については、「Amazon DynamoDB デベロッパーガイド」を参照してください。

IgnoreNullsMode オプションの説明

  • IgnoreNullsMode.SCALAR_ONLY - この設定を使用して、任意のレベルでスカラー属性を更新します。SDK は、null 以外のスカラー属性のみを DynamoDB に送信する更新ステートメントを作成します。SDK は、Bean または Map の null 値のスカラー属性を無視し、DynamoDB に保存された値を保持します。

    Map または Bean のスカラー属性を更新する場合、Map は DynamoDB に既に存在している必要があります。DynamoDB のオブジェクトにまだ存在しないオブジェクトに Map または Bean を追加すると、「更新式で指定されたドキュメントパスが更新に対して無効です」というメッセージと DynamoDbException が表示されます。属性を更新する前に、 MAPS_ONLY モードを使用して DynamoDB に Bean または Map を追加する必要があります。

  • IgnoreNullsMode.MAPS_ONLY - この設定を使用して、 Bean または Map プロパティを追加または置き換えます。SDK は、オブジェクトで提供される Map または Bean を置き換えるか、追加します。オブジェクト内の null の Bean または Map は無視され、DynamoDB に存在する Map が保持されます。

  • IgnoreNullsMode.DEFAULT - この設定では、SDK が null 値を無視することはありません。すべてのレベルの null のスカラー属性は、null に更新されます。SDK は、オブジェクト内の null 値の Bean、Map、List、または Set プロパティを DynamoDB で null に更新します。このモードを使用する場合、またはデフォルトモードであるためモードを指定しない場合、更新用のオブジェクトで提供されている値が DynamoDB で null に設定されないように、まず項目を取得する必要があります。ただし、値を null に設定することが意図されている場合は除きます。

すべてのモードで、null 以外の List または Set を持つオブジェクトを updateItem に提供すると、List または Set は DynamoDB に保存されます。

モードを使用する理由

Bean または Map のあるオブジェクトを updateItem メソッドに渡すと、SDK は Bean のプロパティ値 (または Map 内のエントリ値) を使用して項目を更新するか、または Bean/Map 全体で DynamoDB に保存されたものを置き換える必要があるかを認識できません。

最初に項目を取得することを示した前の例から、取得なしで mainAddresscity 属性を更新してみましょう。

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

次の 2 つの例は、MAPS_ONLYSCALAR_ONLY の列挙値の使用を示しています。MAPS_ONLY は Map を追加し、SCALAR_ONLY は Map を更新します。

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

モードごとに無視される null 値を確認するには、次の表を参照してください。Bean や Map を使用する場合を除き、多くの場合は SCALAR_ONLYMAPS_ONLY のいずれかを使用できます。

各モードで、 updateItem に渡されたオブジェクト内のどの null 値プロパティが SDK によって無視されますか?
プロパティの型 SCALAR_ONLY モードの場合 MAPS_ONLY モードの場合 DEFAULT モードの場合
上位スカラー あり あり いいえ
Bean または Map あり あり いいえ
Bean または Map エントリのスカラー値 はい 1 なし2 いいえ
List または Set あり あり いいえ

1これは、Map が既に DynamoDB に存在することを前提としています。更新のためにオブジェクトで指定した Bean または Map のスカラー値 (null または null 以外) には、値へのパスが DynamoDB に存在する必要があります。SDK は、リクエストを送信する前に . (dot) 参照解除演算子を使用して、属性へのパスを作成します。

2MAPS_ONLY モードを使用して Bean または Map を完全に置き換えたり追加したりするため、Bean または Map 内のすべての null 値は DynamoDB に保存された Map に保持されます。