콩, 맵, 목록 및 세트인 속성 작업 - AWS SDK for Java 2.x

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

콩, 맵, 목록 및 세트인 속성 작업

아래 표시된 Person 클래스와 같은 빈 정의는 추가 속성이 있는 유형을 참조하는 속성(또는 속성)을 정의할 수 있습니다. 예를 들어 클래스에서 Person mainAddress는 추가 값 속성을 정의하는 Address 콩을 나타내는 속성입니다. addresses 는 Java 맵을 나타내며, 이 맵의 요소는 Address 콩을 나타냅니다. 이러한 복잡한 유형은 DynamoDB 의 컨텍스트에서 데이터 값에 사용하는 간단한 속성의 컨테이너를 생각할 수 있습니다.

DynamoDB는 맵, 목록 또는 콩과 같은 중첩 요소의 값 속성을 중첩 속성 으로 지칭합니다. Amazon DynamoDB 개발자 안내서는 Java 맵, 목록 또는 콩의 저장된 형태를 문서 유형으로 참조합니다. Java에서 데이터 값에 사용하는 간단한 속성은 DynamoDB 에서 스칼라 유형이라고 합니다. 동일한 유형의 여러 스칼라 요소를 포함하며 집합 유형이라고 하는 집합입니다.

DynamoDB Enhanced Client는 저장 시 빈 속성을 DynamoDB 맵 문서 유형으로 API 변환한다는 점에 유의해야 합니다.

@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가 다음 조각으로 Person 클래스에 대한 테이블 스키마를 API 빌드하면 는 AddressPhoneNumber 클래스의 사용을 API 검색하고 DynamoDB 와 함께 작동하도록 해당 매핑을 빌드합니다.

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

빌더와 함께 추상 스키마 사용

대체 접근 방식은 다음 코드와 같이 각 중첩된 빈 클래스에 정적 테이블 스키마 빌더를 사용하는 것입니다.

AddressPhoneNumber 클래스의 테이블 스키마는 DynamoDB 테이블과 함께 사용할 수 없다는 점에서 추상적입니다. 기본 키에 대한 정의가 없기 때문입니다. 하지만 Person 클래스의 테이블 스키마에서는 중첩 스키마로 사용됩니다.

PERSON_TABLE_SCHEMA의 정의에서 주석 줄 1과 2줄 뒤에 추상 테이블 스키마를 사용하는 코드가 표시됩니다. EnhanceType.documentOf(...) 메서드documentOf에서 를 사용한다고 해서 메서드가 향상된 문서 EnhancedDocument 유형을 반환한다는 의미는 아닙니다API. 이 컨텍스트의 documentOf(...) 메서드는 테이블 스키마 인수를 사용하여 클래스 인수를 DynamoDB 테이블 속성에 매핑하거나 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 Enhanced Client는 요청이 전송되기 전에 Java 메서드 호출 파라미터를 프로젝션 표현식으로 API 변환합니다.

다음 예제는 Person 테이블을 두 항목으로 채운 다음 세 번의 스캔 작업을 수행합니다.

첫 번째 스캔에서는 결과를 다른 스캔 작업과 비교하기 위해 테이블의 모든 항목에 액세스합니다.

두 번째 스캔에서는 addNestedAttributeToProject() 빌더 메서드를 사용하여 street 속성 값만 반환합니다.

세 번째 스캔 작업에서는 attributesToProject() 빌더 메서드를 사용하여 첫 번째 수준 속성 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]}

표현식에서 복잡한 유형 사용

참조 해제 연산자를 사용하여 복잡한 유형의 구조를 탐색하여 필터 표현식 및 조건 표현식과 같은 복잡한 표현식을 사용할 수 있습니다. 객체 및 맵의 경우 . (dot) 및 를 사용하여 목록 요소를 사용합니다[n](요소의 시퀀스 번호 주변의 대괄호). 세트의 개별 요소를 참조할 수는 없지만 contains 함수 를 사용할 수는 있습니다.

다음 예제는 스캔 작업에 사용되는 두 개의 필터 표현식을 보여줍니다. 필터 표현식은 결과에서 원하는 항목의 일치 조건을 지정합니다. 이 예제에서는 이전에 표시된 Person, AddressPhoneNumber 클래스를 사용합니다.

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

복잡한 유형이 포함된 항목 업데이트

복잡한 유형이 포함된 항목을 업데이트하려면 두 가지 기본 접근 방식이 있습니다.

  • 접근 방식 1: 먼저 항목을 검색하고(를 사용하여getItem) 객체를 업데이트한 다음 를 호출합니다DynamoDbTable#updateItem.

  • 접근 방식 2: 항목을 검색하지 말고 새 인스턴스를 구성하고 업데이트하려는 속성을 설정한 다음 적절한 값을 설정DynamoDbTable#updateItem하여 인스턴스를 IgnoreNullsMode 에 제출합니다. 이 접근 방식은 업데이트하기 전에 항목을 가져올 필요가 없습니다.

이 섹션에 표시된 예제에서는 이전에 표시된 Person, AddressPhoneNumber 클래스를 사용합니다.

업데이트 접근 방식 1: 검색 후 업데이트

이 접근 방식을 사용하면 업데이트 시 데이터가 손실되지 않도록 할 수 있습니다. DynamoDB Enhanced Client는 복합 유형의 값을 포함하여 DynamoDB에 저장된 항목의 속성으로 빈을 API 다시 생성합니다. 그런 다음 getter와 setter를 사용하여 빈을 업데이트해야 합니다. 이 접근 방식의 단점은 먼저 항목을 검색하는 데 드는 비용입니다.

다음 예제는 업데이트하기 전에 항목을 처음 검색하면 데이터가 손실되지 않음을 보여줍니다.

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 값 속성을 지정하려면 를 빌드할 때 열거형IgnoreNullsMode을 제공합니다UpdateItemEnhancedRequest. 열거 값 중 하나를 사용하는 예제로 다음 조각은 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" } } */

Amazon DynamoDB 개발자 안내서에는 업데이트 표현식에 대한 자세한 정보가 포함되어 있습니다.

IgnoreNullsMode 옵션에 대한 설명

  • IgnoreNullsMode.SCALAR_ONLY - 이 설정을 사용하여 모든 수준에서 스칼라 속성을 업데이트합니다. 는 Null이 아닌 스칼라 속성만 DynamoDB 로 전송하는 업데이트 문을 SDK 구성합니다. 는 빈 또는 맵의 null 값, 스칼라 속성을 SDK 무시하여 DynamoDB 에 저장된 값을 유지합니다.

    맵 또는 빈의 스칼라 속성을 업데이트할 때 맵이 DynamoDB 에 이미 있어야 합니다. DynamoDB의 객체에 대해 아직 존재하지 않는 객체에 맵 또는 빈을 추가하는 경우 업데이트 표현식에 제공된 문서 경로가 업데이트에 유효하지 않습니다라는 메시지가 DynamoDbException 에 표시됩니다. 속성을 업데이트하기 전에 MAPS_ONLY 모드를 사용하여 DynamoDB에 빈 또는 맵을 추가해야 합니다.

  • IgnoreNullsMode.MAPS_ONLY - 이 설정을 사용하여 콩 또는 맵인 속성을 추가하거나 바꿉니다. 는 객체에 제공된 맵 또는 빈을 SDK 대체하거나 추가합니다. 객체에 null인 모든 빈 또는 맵은 무시되어 DynamoDB 에 있는 맵을 유지합니다.

  • IgnoreNullsMode.DEFAULT - 이 설정을 사용하면 는 null 값을 무시SDK하지 않습니다. null인 모든 수준의 Scalar 속성은 null로 업데이트됩니다. 는 객체의 null 값 빈, 맵, 목록 또는 속성을 DynamoDB의 null로 SDK 업데이트합니다. 이 모드를 사용하거나 기본 모드이므로 모드를 제공하지 않는 경우, 값을 null로 설정하려는 의도가 아니라면 DynamoDB의 값이 업데이트를 위해 객체에 제공된 null로 설정되지 않도록 먼저 항목을 검색해야 합니다.

모든 모드에서 null이 아닌 목록 또는 세트가 updateItem 있는 객체를 에 제공하면 목록 또는 세트가 DynamoDB 에 저장됩니다.

모드가 필요한 이유는 무엇입니까?

객체에 빈 또는 맵을 updateItem 메서드에 제공할 때 는 빈의 속성 값(또는 맵의 항목 값)을 사용하여 항목을 업데이트해야 하는지 또는 전체 빈/맵이 DynamoDB에 저장된 항목을 대체해야 하는지 알 SDK 수 없습니다.

항목 검색을 먼저 보여주는 이전 예제에서 작업하면서 검색 mainAddress 없이 의 city 속성을 업데이트해 보겠습니다.

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

다음 두 예제에서는 MAPS_ONLYSCALAR_ONLY 열거형 값의 사용을 보여줍니다. 는 맵을 MAPS_ONLY 추가하고 맵을 SCALAR_ONLY 업데이트합니다.

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 값을 확인하려면 다음 표를 참조하세요. 빈 또는 맵으로 작업할 때를 SCALAR_ONLY MAPS_ONLY 제외하고 및 로 작업할 수 있습니다.

에 제출된 객체의 어떤 null 값 속성updateItem이 각 모드에 대해 SDK 무시됩니까?
속성 유형 SCALAR_ONLY 모드에서 MAPS_ONLY 모드에서 DEFAULT 모드에서
상위 스칼라 아니요
빈 또는 맵 아니요
콩 또는 맵 항목의 스칼라 값 1 2 아니요
목록 또는 세트 아니요

1이는 맵이 DynamoDB 에 이미 있다고 가정합니다. 업데이트를 위해 객체에 제공하는 빈 또는 맵의 스칼라 값인 null 또는 null이 아닌 모든 스칼라 값은 값의 경로가 DynamoDB 에 있어야 합니다. 는 요청을 제출하기 전에 참조 . (dot) 해제 연산자를 사용하여 속성에 대한 경로를 SDK 구성합니다.

2MAPS_ONLY모드를 사용하여 빈 또는 맵을 완전히 교체하거나 추가하면 빈 또는 맵의 모든 null 값이 DynamoDB 에 저장된 맵에 유지됩니다.