Java의 DynamoDB Query (AWS SDK v2)
AWS SDK for Java 2.x의 queryPaginator는 컬렉션처럼 보이지만 컬렉션이 아닙니다. 지연 평가되는 재순회 가능 객체이며, 그 차이는 두 번째로 순회하는 순간 청구서에 드러납니다. 애초에 Query가 맞는 읽기인지에 대해서는 Query vs. Scan을 보세요.
코드
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
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.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
public class QueryExample {
public static void main(String[] args) {
try (DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build()) {
Map<String, String> names = new HashMap<>();
names.put("#hashKey", "Artist");
names.put("#rangeKey", "SongTitle");
Map<String, AttributeValue> values = new HashMap<>();
values.put(":hashKeyValue", AttributeValue.builder().s("Arturo Sandoval").build());
values.put(":rangeKeyValue", AttributeValue.builder().s("C").build());
QueryRequest request = QueryRequest.builder()
.tableName("Music")
.keyConditionExpression(
"#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)")
.expressionAttributeNames(names)
.expressionAttributeValues(values)
.build();
List<Map<String, AttributeValue>> items = new ArrayList<>();
for (QueryResponse page : ddb.queryPaginator(request)) {
items.addAll(page.items());
}
System.out.println("Found " + items.size() + " items");
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
}
}
}QueryIterable은 순회할 때마다 쿼리를 다시 실행합니다
곡마다 약 3.9 KB이고 모두 Artist = "Arturo Sandoval" 아래에 있는 600곡짜리 픽스처에 대해, ddb.queryPaginator(request)는 2.6 ms 만에 반환되었고 아무것도 보내지 않았습니다. 그런 다음 동일한 QueryIterable 객체를 두 번 순회했습니다:
queryPaginator(request) returned in 2.60 ms (class QueryIterable, 0 requests)
iteration 1: pages=3 items=600 capacity=284.5
iteration 2: pages=3 items=600 capacity=284.5 <- same object변수 하나에 대한 for 루프 두 개, 읽기 단위 569. 페이지는 전혀 캐시되지 않았고, 순회할 때마다 LastEvaluatedKey를 처음부터 다시 따라갑니다. 항목이 두 번 필요하다면 예제처럼 순회 가능 객체를 한 번만 List로 비워 담으세요.
같은 지연 평가의 이면은 오류가 드러나는 지점입니다. 키 조건에서 파티션 키를 빠뜨린 요청을 만들어도 queryPaginator는 불평 없이 받아들입니다. 아직 아무 호출도 일어나지 않았기 때문입니다:
queryPaginator(bad) constructed without throwing
threw on iteration: DynamoDbException / ValidationException /
Query condition missed key schema element / http 400빌더를 감싼 try-catch는 아무것도 잡지 못합니다. 루프를 감싸야 하며, 그래서 예제는 전체 블록을 하나의 try 안에 넣습니다.
2026-07-28에 OpenJDK 26.0.1의 software.amazon.awssdk:dynamodb 2.49.4로 DynamoDB Local(amazon/dynamodb-local)에 대해 측정했습니다.
설명
ddb.queryPaginator(request).items()는 페이지를Iterable<Map<String, AttributeValue>>로 펼치고 뒤에서 페이지네이션을 처리하므로, 항목만 필요할 때는 예제의 2단계 루프가 하나로 줄어듭니다.SdkIterable이기도 하므로.stream()도 동작합니다.- 빌더에서 숫자는
String입니다.AttributeValue.builder().n("1994")는.n(1994)의 오타가 아닙니다.n()설정자는java.lang.String을 받는데, DynamoDB가 이진 부동소수점 반올림을 피하려고 숫자를 십진 텍스트로 전송하기 때문입니다. Javaint를 넘기면 컴파일되지 않습니다. keyConditionExpression은 파티션 키에 대한 등호와 정렬 키 조건을 최대 하나(=,<,<=,>,>=,BETWEEN,begins_with) 받습니다..scanIndexForward(false)는 순서를 뒤집고,.indexName("...")은 보조 인덱스로 대상을 바꿉니다.- Enhanced Client는 또 다른 사용성 선택지입니다.
software.amazon.awssdk.enhanced.dynamodb는Map<String, AttributeValue>대신 애너테이션이 붙은 빈을 매핑하며, 그query는 위에서 측정한 것과 동일한 지연 재순회 의미론을 가진PageIterable<T>를 반환합니다.
시각적으로 해보기
이 예제의 #hashKey와 #rangeKey는 어느 것도 강제되지 않은 별칭입니다. Artist도 SongTitle도 AWS의 573개 예약어 목록에 없습니다. 예약어 검사기는 여러분의 속성 이름 중 정말로 # 처리가 필요한 것이 무엇인지 알려 주므로, 별칭 맵이 관행적 흉내에 그치지 않게 해 줍니다.
QueryRequest를 만들기 전에 실제 테이블에 키 조건을 시험해 보려면 DynoTable을 다운로드해서 결과를 그리드에서 페이지로 넘겨 보세요.
관련 예제
- Go의 DynamoDB Query — AWS SDK for Go v2로 하는 동일한 쿼리.
- Java의 DynamoDB Scan — 파티션으로 키를 잡을 수 없을 때.
- Query vs. Scan —
Query가 올바른 기본값인 이유. - 키 조건 표현식 — 사용할 수 있는 모든 파티션/정렬 키 연산자.
- "Query condition missed key schema element" — 키 조건이 잘못된 속성을 지정했거나 파티션 키를 건너뛴 경우.
- "Query key condition not supported" — contains나 두 번째 정렬 키 조건처럼 키 조건이 쓸 수 없는 연산자.