Java(AWS SDK v2)의 DynamoDB UpdateItem

업데이트 자체는 빌더 호출 하나입니다. Java 개발자의 시간을 잡아먹는 것은 그 주변의 모든 것입니다. 결코 null을 반환하지 않는 응답 객체, 정작 흥미로운 실패가 여러분이 이미 잡았을 예외의 하위 클래스인 예외 계층, 그리고 이 작업을 아예 표현하지 못하는 고수준 클라이언트 말입니다.

코드

import java.util.HashMap;
import java.util.Map;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ReturnValue;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;

public class UpdateItemExample {
    public static void main(String[] args) {
        try (DynamoDbClient ddb = DynamoDbClient.builder()
                .region(Region.US_EAST_1)
                .build()) {

            Map<String, AttributeValue> key = new HashMap<>();
            key.put("Artist", AttributeValue.builder().s("Arturo Sandoval").build());
            key.put("SongTitle", AttributeValue.builder().s("Cubano Chant").build());

            Map<String, String> names = new HashMap<>();
            names.put("#upd0", "Genre");
            names.put("#upd1", "Year");
            names.put("#upd2", "Awards");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":updValue0", AttributeValue.builder().s("Latin Jazz").build());
            values.put(":updValue1", AttributeValue.builder().n("1994").build());
            values.put(":updValue2", AttributeValue.builder().n("1").build());

            UpdateItemRequest request = UpdateItemRequest.builder()
                    .tableName("Music")
                    .key(key)
                    .updateExpression("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2")
                    .expressionAttributeNames(names)
                    .expressionAttributeValues(values)
                    .returnValues(ReturnValue.ALL_NEW)
                    .build();

            UpdateItemResponse response = ddb.updateItem(request);
            System.out.println(response.attributes()); // the item after the update
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
    }
}

설명

  • AttributeValue.builder().n("1994")String을 받으며, 더 짧은 AttributeValue.fromN("1994")도 마찬가지입니다. n(int) 오버로드는 없습니다. DynamoDB 숫자는 유효 숫자 38자리를 담는데 Java 기본형은 그렇지 못하기 때문입니다. 되읽을 때도 attributes().get("Awards").n()String입니다. 잘못된 타입의 접근자는 예외 대신 null을 반환하므로 숫자에 .s()를 쓰면 조용한 null이 되고, 어느 쪽이 설정되었는지는 .type()이 알려줍니다.

  • response.attributes()는 결코 null을 반환하지 않습니다. ReturnValue.NONE이면 비어 있지만 null은 아닌 DefaultSdkAutoConstructMap을 반환하므로 null 검사는 절대 걸리지 않고, isEmpty() 검사로는 "서비스가 아무것도 보내지 않았다"와 "항목에 속성이 없다"를 구분할 수 없습니다. 그 차이를 아는 접근자는 생성된 hasAttributes()입니다. 이 SDK의 모든 컬렉션 멤버에는 그런 접근자가 하나씩 있습니다.

  • 빌더는 정작 중요한 부분만 빼고 전부 타입 검사를 합니다. updateExpression(String)은 아무 문자열이나 받아들이며, 컴파일러는 SET과 오타를 구별하지 못하므로 표현식 실수는 런타임 400이 됩니다. ADD #upd2 :updValue2가 원자적 증가이고, attribute_exists(Artist)conditionExpression은 호출을 업데이트 전용으로 만들며, 문법은 업데이트 표현식에 있습니다.

  • 레거시 attributeUpdates 맵보다 표현식을 쓰세요. 오래된 예제에는 여전히 그것이 보입니다. 그것으로는 여러 절 유형, 별칭, 조건을 한 요청에 표현할 수 없습니다.

  • getMessage()는 서비스의 메시지가 아닙니다. SDK가 자신의 전송 세부 정보를 덧붙입니다:

    The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: d99b117c-edd6-4dc9-8d3a-a5fa4fe9666c) (SDK Attempt Count: 1)

    지원 케이스용 요청 ID가 필요하다면 그것을 기록하세요. 비교는 awsErrorDetails().errorCode()로 하고, 순수한 문자열이 필요할 때는 awsErrorDetails().errorMessage()를 쓰세요.

여기서는 catch 순서가 평소보다 더 중요합니다

ConditionalCheckFailedException extends DynamoDbException이므로, catch (DynamoDbException e)를 앞에 두면 여러분이 거의 확실히 분기하고 싶었던 그 실패 하나를 삼켜 버립니다. 구체적인 타입을 먼저 잡고, 온 김에 항목도 가져오세요:

} catch (ConditionalCheckFailedException e) {
    // with .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
    if (e.hasItem()) {
        Map<String, AttributeValue> loser = e.item();  // the item as it actually was
    }
} catch (DynamoDbException e) {
    // everything else
}

이 예외에서 e.retryable()false이며, 그것이 맞습니다. 실패한 조건을 재시도해 봐야 또 실패할 뿐입니다.

기억해야 할 비대칭은 이 SDK에 ValidationException 클래스가 없다는 것입니다. dynamodb-2.35.9.jar를 뒤져도 잡을 것이 없습니다. 예약어, 잘못된 형식의 표현식, 부분 키 — 전부 awsErrorDetails().errorCode()가 우연히 ValidationException이라고 읽히는 평범한 DynamoDbException으로 도착합니다. 정적 타입 언어에서 이는 거슬리는 구멍이며, 표현식 실수가 런타임 문자열 비교가 된다는 뜻입니다.

그래서 DynamoDB의 예약어는 출시 후가 아니라 출시 전에 한 번 훑어볼 가치가 있습니다. 목록은 573개에 이르며 Year, Name, Status가 들어 있는데, 어느 것도 Java 빈에서는 위험해 보이지 않습니다. 빈 매핑 대신 원시 테이블을 탐색하려면, DynoTable을 다운로드하세요.

Enhanced Client는 이것을 표현하지 못합니다

나머지 데이터 액세스가 전부 DynamoDbEnhancedClient와 애너테이션이 붙은 빈을 거친다면, 이 작업이 여러분을 DynamoDbClient로 되돌려 보내는 그 하나입니다. UpdateItemEnhancedRequest.Builder를 리플렉션으로 살펴보면 item, conditionExpression, ignoreNulls, ignoreNullsMode, returnValues, returnValuesOnConditionCheckFailure, returnConsumedCapacity, returnItemCollectionMetrics가 나옵니다. 업데이트 표현식을 받는 메서드는 없습니다.

실질적인 결과는 원자적 카운터입니다. ADD #upd2 :updValue2는 먼저 읽지 않고 서버 측에서 Awards를 증가시킵니다. Enhanced Client가 주는 것은 매핑된 빈과, 없는 필드를 제거할지 결정하는 ignoreNulls뿐이며, ADD로 컴파일되는 것은 아무것도 없습니다. 빈을 통한 읽기-수정-쓰기는 동시성 아래에서 갱신 손실 경합이고, 이 페이지의 스니펫이 피하는 것이 정확히 그것입니다.

관련 예제

참고 자료

위에 링크된 공식 AWS 문서를 기준으로 2026-07-28에 마지막으로 검증했습니다.

Console 없이 DynamoDB 작업하기

DynamoDB로는 실행할 수 없는 진짜 SQL(JOINs, GROUP BY, 집계)을 실행하는 빠른 DynamoDB 데스크톱 클라이언트. 시각적 편집과 여러분 자신의 Bedrock 키로 동작하는 AI 에이전트를 제공합니다.

30일 무료 체험, 신용카드 불필요 — 이후 기간 제한 없는 무료 요금제.