Trabalhe com atributos que são beans, mapas, listas e conjuntos - AWS SDK for Java 2.x

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

Trabalhe com atributos que são beans, mapas, listas e conjuntos

Uma definição de bean, como a Person classe mostrada abaixo, pode definir propriedades (ou atributos) que se referem a tipos com atributos adicionais. Por exemplo, na Person classe, mainAddress é uma propriedade que se refere a um Address bean que define atributos de valor adicionais. addressesrefere-se a um mapa Java, cujos elementos se referem a Address beans. Esses tipos complexos podem ser considerados contêineres de atributos simples que você usa como valor de dados no contexto do DynamoDB.

O DynamoDB se refere às propriedades de valor de elementos aninhados, como mapas, listas ou beans, como atributos aninhados. O Amazon DynamoDB Developer Guide se refere à forma salva de um mapa, lista ou bean Java como um tipo de documento. Os atributos simples que você usa para seu valor de dados em Java são chamados de tipos escalares no DynamoDB. Conjuntos, que contêm vários elementos escalares do mesmo tipo e chamados de tipos de conjuntos.

É importante saber que o DynamoDB Enhanced API Client converte uma propriedade que é bean em um tipo de documento de mapa do DynamoDB quando ela é salva.

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

Salve tipos complexos

Use classes de dados anotadas

Você salva atributos aninhados para classes personalizadas simplesmente anotando-os. A classe Address e a classe PhoneNumber mostradas anteriormente são anotadas somente com a anotação @DynamoDbBean. Quando o DynamoDB Enhanced API Client cria o esquema de tabela para a classe com Person o seguinte trecho, ele descobre o uso das classes PhoneNumber e e cria API os mapeamentos correspondentes para trabalhar com Address o DynamoDB.

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

Use esquemas abstratos com construtores

A abordagem alternativa é usar construtores de esquemas de tabelas estáticas para cada classe de bean aninhado, conforme mostrado no código a seguir.

Os esquemas de tabela das classes Address e PhoneNumber são abstratos, uma vez que não podem ser usados com uma tabela do DynamoDB. Isso ocorre porque eles não têm definições para a chave primária. No entanto, eles são usados como esquemas aninhados no esquema de tabela da classe Person.

Depois de comentar as linhas 1 e 2 na definição de PERSON_TABLE_SCHEMA, você vê o código que usa os esquemas de tabela abstrata. O uso de documentOf no EnhanceType.documentOf(...) método não indica que o método retorne qualquer EnhancedDocument tipo do documento aprimoradoAPI. O método documentOf(...) nesse contexto retorna um objeto que sabe como mapear seu argumento de classe de e para os atributos da tabela do DynamoDB usando o argumento do esquema da tabela.

// Abstract table schema that cannot be used to work with a DynamoDB table, // but can be used as a nested schema. public static final TableSchema<Address> TABLE_SCHEMA_ADDRESS = TableSchema.builder(Address.class) .newItemSupplier(Address::new) .addAttribute(String.class, a -> a.name("street") .getter(Address::getStreet) .setter(Address::setStreet)) .addAttribute(String.class, a -> a.name("city") .getter(Address::getCity) .setter(Address::setCity)) .addAttribute(String.class, a -> a.name("zipcode") .getter(Address::getZipCode) .setter(Address::setZipCode)) .addAttribute(String.class, a -> a.name("state") .getter(Address::getState) .setter(Address::setState)) .build(); // Abstract table schema that cannot be used to work with a DynamoDB table, // but can be used as a nested schema. public static final TableSchema<PhoneNumber> TABLE_SCHEMA_PHONENUMBER = TableSchema.builder(PhoneNumber.class) .newItemSupplier(PhoneNumber::new) .addAttribute(String.class, a -> a.name("type") .getter(PhoneNumber::getType) .setter(PhoneNumber::setType)) .addAttribute(String.class, a -> a.name("number") .getter(PhoneNumber::getNumber) .setter(PhoneNumber::setNumber)) .build(); // A static table schema that can be used with a DynamoDB table. // The table schema contains two nested schemas that are used to perform mapping to/from DynamoDB. public static final TableSchema<Person> PERSON_TABLE_SCHEMA = TableSchema.builder(Person.class) .newItemSupplier(Person::new) .addAttribute(Integer.class, a -> a.name("id") .getter(Person::getId) .setter(Person::setId) .addTag(StaticAttributeTags.primaryPartitionKey())) .addAttribute(String.class, a -> a.name("firstName") .getter(Person::getFirstName) .setter(Person::setFirstName)) .addAttribute(String.class, a -> a.name("lastName") .getter(Person::getLastName) .setter(Person::setLastName)) .addAttribute(Integer.class, a -> a.name("age") .getter(Person::getAge) .setter(Person::setAge)) .addAttribute(EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS), a -> a.name("mainAddress") .getter(Person::getMainAddress) .setter(Person::setMainAddress)) .addAttribute(EnhancedType.listOf(String.class), a -> a.name("hobbies") .getter(Person::getHobbies) .setter(Person::setHobbies)) .addAttribute(EnhancedType.mapOf( EnhancedType.of(String.class), // 1. Use mapping functionality of the Address table schema. EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS)), a -> a.name("addresses") .getter(Person::getAddresses) .setter(Person::setAddresses)) .addAttribute(EnhancedType.listOf( // 2. Use mapping functionality of the PhoneNumber table schema. EnhancedType.documentOf(PhoneNumber.class, TABLE_SCHEMA_PHONENUMBER)), a -> a.name("phoneNumbers") .getter(Person::getPhoneNumbers) .setter(Person::setPhoneNumbers)) .build();

Atributos de projeto de tipos complexos

Para os métodos query() e scan(), você pode especificar quais atributos você deseja que sejam retornados nos resultados usando chamadas de método como addNestedAttributeToProject() e attributesToProject(). O DynamoDB Enhanced API Client converte os parâmetros de chamada do método Java em expressões de projeção antes que a solicitação seja enviada.

O exemplo a seguir preenche a tabela Person com dois itens e, em seguida, executa três operações de verificação.

A primeira verificação acessa todos os itens da tabela para comparar os resultados com as outras operações de verificação.

A segunda verificação usa o método do construtor addNestedAttributeToProject() para retornar somente o valor do atributo street.

A terceira operação de varredura usa o método do construtor attributesToProject() para retornar os dados do atributo de primeiro nível, hobbies. O tipo do atributo de hobbies é uma lista. Para acessar itens individuais da lista, execute uma operação get() na lista.

personDynamoDbTable = getDynamoDbEnhancedClient().table("Person", PERSON_TABLE_SCHEMA); PersonUtils.createPersonTable(personDynamoDbTable, getDynamoDbClient()); // Use a utility class to add items to the Person table. List<Person> personList = PersonUtils.getItemsForCount(2); // This utility method performs a put against DynamoDB to save the instances in the list argument. PersonUtils.putCollection(getDynamoDbEnhancedClient(), personList, personDynamoDbTable); // The first scan logs all items in the table to compare to the results of the subsequent scans. final PageIterable<Person> allItems = personDynamoDbTable.scan(); allItems.items().forEach(p -> // 1. Log what is in the table. logger.info(p.toString())); // Scan for nested attributes. PageIterable<Person> streetScanResult = personDynamoDbTable.scan(b -> b // Use the 'addNestedAttributeToProject()' or 'addNestedAttributesToProject()' to access data nested in maps in DynamoDB. .addNestedAttributeToProject( NestedAttributeName.create("addresses", "work", "street") )); streetScanResult.items().forEach(p -> //2. Log the results of requesting nested attributes. logger.info(p.toString())); // Scan for a top-level list attribute. PageIterable<Person> hobbiesScanResult = personDynamoDbTable.scan(b -> b // Use the 'attributesToProject()' method to access first-level attributes. .attributesToProject("hobbies")); hobbiesScanResult.items().forEach((p) -> { // 3. Log the results of the request for the 'hobbies' attribute. logger.info(p.toString()); // To access an item in a list, first get the parent attribute, 'hobbies', then access items in the list. String hobby = p.getHobbies().get(1); // 4. Log an item in the list. logger.info(hobby); });
// Logged results from comment line 1. Person{id=2, firstName='first name 2', lastName='last name 2', age=11, addresses={work=Address{street='street 21', city='city 21', state='state 21', zipCode='33333'}, home=Address{street='street 2', city='city 2', state='state 2', zipCode='22222'}}, phoneNumbers=[PhoneNumber{type='home', number='222-222-2222'}, PhoneNumber{type='work', number='333-333-3333'}], hobbies=[hobby 2, hobby 21]} Person{id=1, firstName='first name 1', lastName='last name 1', age=11, addresses={work=Address{street='street 11', city='city 11', state='state 11', zipCode='22222'}, home=Address{street='street 1', city='city 1', state='state 1', zipCode='11111'}}, phoneNumbers=[PhoneNumber{type='home', number='111-111-1111'}, PhoneNumber{type='work', number='222-222-2222'}], hobbies=[hobby 1, hobby 11]} // Logged results from comment line 2. Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null} Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null} // Logged results from comment lines 3 and 4. Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]} hobby 21 Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]} hobby 11
nota

Se o método attributesToProject() seguir qualquer outro método do construtor que adiciona atributos que você deseja projetar, a lista de nomes de atributos fornecida ao attributesToProject() substitui todos os outros nomes de atributos.

Uma verificação realizada com a instância ScanEnhancedRequest no trecho a seguir retorna somente dados de hobby.

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

O trecho de código a seguir usa o método attributesToProject() primeiro. Essa ordenação preserva todos os outros atributos obrigatórios.

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

Use tipos complexos em expressões

Você pode usar tipos complexos em expressões, como expressões de filtro e expressões condicionais, usando operadores de desreferenciamento para navegar pela estrutura do tipo complexo. Para objetos e mapas, use o . (dot) e para elementos da lista use [n] (colchetes ao redor do número de sequência do elemento). Você não pode se referir a elementos individuais de um conjunto, mas você pode usar a containsfunção.

O exemplo a seguir mostra duas expressões de filtro que são usadas em operações de varredura. As expressões de filtro especificam as condições de correspondência dos itens que você deseja nos resultados. O exemplo usa PersonAddress, e PhoneNumber classes mostradas anteriormente.

public void scanUsingFilterOfNestedAttr() { // The following is a filter expression for an attribute that is a map of Address objects. // By using this filter expression, the SDK returns Person objects that have an address // with 'mailing' as a key and 'MS2' for a state value. Expression addressFilter = Expression.builder() .expression("addresses.#type.#field = :value") .putExpressionName("#type", "mailing") .putExpressionName("#field", "state") .putExpressionValue(":value", AttributeValue.builder().s("MS2").build()) .build(); PageIterable<Person> addressFilterResults = personDynamoDbTable.scan(rb -> rb. filterExpression(addressFilter)); addressFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p)); assert addressFilterResults.items().stream().count() == 1; // The following is a filter expression for an attribute that is a list of phone numbers. // By using this filter expression, the SDK returns Person objects whose second phone number // in the list has a type equal to 'cell'. Expression phoneFilter = Expression.builder() .expression("phoneNumbers[1].#type = :type") .putExpressionName("#type", "type") .putExpressionValue(":type", AttributeValue.builder().s("cell").build()) .build(); PageIterable<Person> phoneFilterResults = personDynamoDbTable.scan(rb -> rb .filterExpression(phoneFilter) .attributesToProject("id", "firstName", "lastName", "phoneNumbers") ); phoneFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p)); assert phoneFilterResults.items().stream().count() == 1; assert phoneFilterResults.items().stream().findFirst().get().getPhoneNumbers().get(1).getType().equals("cell"); }
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" } ] }

Atualizar itens que contêm tipos complexos

Para atualizar um item que contém tipos complexos, você tem duas abordagens básicas:

  • Abordagem 1: primeiro recupere o item (usandogetItem), atualize o objeto e, em seguida, chameDynamoDbTable#updateItem.

  • Abordagem 2: Não recupere o item, mas construa uma nova instância, defina as propriedades que você deseja atualizar e envie a instância DynamoDbTable#updateItem definindo o valor apropriado de IgnoreNullsMode. Essa abordagem não exige que você busque o item antes de atualizá-lo.

Os exemplos mostrados nesta seção usam as PhoneNumber classes PersonAddress, e mostradas anteriormente.

Abordagem de atualização 1: recuperar e, em seguida, atualizar

Ao usar essa abordagem, você garante que nenhum dado seja perdido na atualização. O DynamoDB Enhanced API Client recria o bean com os atributos do item salvo no DynamoDB, incluindo valores de tipos complexos. Em seguida, você precisa usar os getters e setters para atualizar o bean. A desvantagem dessa abordagem é o custo em que você incorre ao recuperar o item primeiro.

O exemplo a seguir demonstra que nenhum dado será perdido se você recuperar o item pela primeira vez antes de atualizá-lo.

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

Abordagem de atualização 2: use um IgnoreNullsMode enum sem recuperar o item primeiro

Para atualizar um item no DynamoDB, você pode fornecer um novo objeto que tenha somente as propriedades que você deseja atualizar e deixar os outros valores como nulos. Com essa abordagem, você precisa estar ciente de como os valores nulos no objeto são tratados pelo SDK e como você pode controlar o comportamento.

Para especificar quais propriedades de valor nulo você deseja ignorar, forneça uma IgnoreNullsMode enumeração ao criar o. SDK UpdateItemEnhancedRequest Como exemplo do uso de um dos valores enumerados, o trecho a seguir usa o modo. IgnoreNullsMode.SCALAR_ONLY

// Create a new Person object to update the existing item in DynamoDB. Person personForUpdate = new Person(); personForUpdate.setId(1); personForUpdate.setFirstName("updatedFirstName"); // 'firstName' is a top scalar property. Address addressForUpdate = new Address(); addressForUpdate.setCity("updatedCity"); personForUpdate.setMainAddress(addressForUpdate); personDynamoDbTable.updateItem(r -> r .item(personForUpdate) .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY) .build()); /* 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" } } */

O Amazon DynamoDB Developer Guide contém mais informações sobre expressões de atualização.

Descrições das IgnoreNullsMode opções

  • IgnoreNullsMode.SCALAR_ONLY- Use essa configuração para atualizar atributos escalares em qualquer nível. Ele SDK constrói uma instrução de atualização que envia somente atributos escalares não nulos para o DynamoDB. Ele SDK ignora os atributos escalares de valor nulo de um bean ou mapa, mantendo o valor salvo no DynamoDB.

    Quando você atualiza um atributo escalar de map ou bean, o mapa já deve existir no DynamoDB. Se você adicionar um mapa ou um bean ao objeto que ainda não existe para o objeto no DynamoDB, você receberá DynamoDbException um com a mensagem O caminho do documento fornecido na expressão de atualização é inválido para atualização. Você deve usar o MAPS_ONLY mode para adicionar um bean ou um mapa ao DynamoDB antes de atualizar qualquer um de seus atributos.

  • IgnoreNullsMode.MAPS_ONLY- Use essa configuração para adicionar ou substituir propriedades que são um bean ou um mapa. O SDK substitui ou adiciona qualquer mapa ou bean fornecido no objeto. Todos os beans ou mapas que são nulos no objeto são ignorados, mantendo o mapa que existe no DynamoDB.

  • IgnoreNullsMode.DEFAULT- Com essa configuração, o SDK nunca ignora valores nulos. Os atributos escalares em qualquer nível que sejam nulos são atualizados para nulos. Ele SDK atualiza qualquer bean, mapa, lista ou propriedade de conjunto com valor nulo no objeto para null no DynamoDB. Ao usar esse modo, ou não fornecer um modo, pois é o modo padrão, você deve recuperar o item primeiro para que os valores no DynamoDB não sejam definidos como nulos que são fornecidos no objeto para atualização, a menos que sua intenção seja definir os valores como nulos.

Em todos os modos, se você fornecer um objeto updateItem que tenha uma lista ou conjunto não nulo, a lista ou o conjunto será salvo no DynamoDB.

Por que os modos?

Quando você fornece a um objeto um bean ou um mapa para o updateItem método, eles não SDK sabem se ele deve usar os valores da propriedade no bean (ou os valores de entrada no mapa) para atualizar o item, ou se o bean/mapa inteiro deve substituir o que foi salvo no DynamoDB.

Trabalhando com nosso exemplo anterior, que mostra primeiro a recuperação do item, vamos tentar atualizar o city atributo de mainAddress sem a recuperação.

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

Os dois exemplos a seguir mostram os usos dos valores MAPS_ONLY e SCALAR_ONLY enumerados. MAPS_ONLYadiciona um mapa e SCALAR_ONLY atualiza um mapa.

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

Consulte a tabela a seguir para ver quais valores nulos são ignorados em cada modo. Muitas vezes, você pode trabalhar com qualquer um deles SCALAR_ONLYMAPS_ONLY, exceto quando trabalha com feijões ou mapas.

Quais propriedades de valor nulo no objeto enviado são SDK ignoradas updateItem em cada modo?
Tipo de propriedade no ONLY modo SCALAR _ no ONLY modo MAPS _ em DEFAULT modo
Escalar superior Sim Sim Não
Feijão ou mapa Sim Sim Não
Valor escalar de um bean ou entrada de mapa Sim1 Não 2 Não
Listar ou definir Sim Sim Não

1 Isso pressupõe que o mapa já exista no DynamoDB. Qualquer valor escalar — nulo ou não nulo — do bean ou mapa que você fornece no objeto para atualização exige que exista um caminho para o valor no DynamoDB. Ele SDK constrói um caminho para o atributo usando o operador de . (dot) desreferenciação antes de enviar a solicitação.

2 Como você usa o MAPS_ONLY modo para substituir totalmente ou adicionar um bean ou mapa, todos os valores nulos no bean ou no mapa são retidos no mapa salvo no DynamoDB.