기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.
페이지 매김된 결과 작업: 스캔 및 쿼리
DynamoDB Enhanced Client의 scan
, query
및 batch
메서드는 하나 이상의 페이지가 있는 응답을 API 반환합니다. 페이지에는 하나 이상의 항목이 포함되어 있습니다. 코드는 페이지별로 응답을 처리하거나 개별 항목을 처리할 수 있습니다.
동기 DynamoDbEnhancedClient
클라이언트에서 반환하는 페이지 매김 응답은 PageIterableDynamoDbEnhancedAsyncClient
반환합니다.
이 섹션에서는 페이지 매김된 결과 처리를 살펴보고 스캔 및 쿼리를 사용하는 예제를 제공합니다APIs.
테이블 스캔
SDK의 scan
먼저 동기 매핑 클래스의 scan
방법인 를 살펴보고 PageIterable
인터페이스를 살펴봅니다DynamoDbTable
동기 사용 API
다음 예제는 표현식scan
메서드를 보여줍니다. ProductCatalog 는 이전에 표시된 모델 객체입니다.
주석 줄 2 뒤에 표시되는 필터링 표현식은 반환되는 ProductCatalog
항목을 가격 값이 8.00~80.00인 항목으로 제한합니다.
이 예제에서는 주석 줄 1 다음에 표시된 attributesToProject
메서드를 사용하여 isbn
값을 제외합니다.
주석 줄 3 다음에 PageIterable
객체인 pagedResults
가 scan
메서드에 의해 반환됩니다. PageIterable
의 stream
메서드는 페이지를 처리하는 데 사용할 수 있는 java.util.Stream
이 예제는 주석 줄 4부터 시작하여 ProductCatalog
항목 액세스의 두 가지 변형을 보여줍니다. 주석 줄 4a 이후의 버전은 각 페이지를 통해 스트리밍되고 각 페이지의 항목을 정렬하고 기록합니다. 주석 줄 4b 이후의 버전은 페이지 반복을 건너뛰고 항목에 직접 액세스합니다.
PageIterable
인터페이스는 두 개의 상위 인터페이스 java.lang.Iterable
SdkIterable
Iterable
는 forEach
, iterator
및 spliterator
메서드를 가져오고 SdkIterable
은 stream
메서드를 가져옵니다.
public static void scanSync(DynamoDbTable<ProductCatalog> productCatalog) { Map<String, AttributeValue> expressionValues = Map.of( ":min_value", numberValue(8.00), ":max_value", numberValue(80.00)); ScanEnhancedRequest request = ScanEnhancedRequest.builder() .consistentRead(true) // 1. the 'attributesToProject()' method allows you to specify which values you want returned. .attributesToProject("id", "title", "authors", "price") // 2. Filter expression limits the items returned that match the provided criteria. .filterExpression(Expression.builder() .expression("price >= :min_value AND price <= :max_value") .expressionValues(expressionValues) .build()) .build(); // 3. A PageIterable object is returned by the scan method. PageIterable<ProductCatalog> pagedResults = productCatalog.scan(request); logger.info("page count: {}", pagedResults.stream().count()); // 4. Log the returned ProductCatalog items using two variations. // 4a. This version sorts and logs the items of each page. pagedResults.stream().forEach(p -> p.items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach( item -> logger.info(item.toString()) )); // 4b. This version sorts and logs all items for all pages. pagedResults.items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach( item -> logger.info(item.toString()) ); }
비동기식 사용 API
비동기 scan
메서드는 결과를 PagePublisher
객체로 반환합니다. PagePublisher
인터페이스에는 응답 페이지를 처리하는 데 사용할 수 있는 두 가지 subscribe
메서드가 있습니다. 한 가지 subscribe
메서드는 org.reactivestreams.Publisher
상위 인터페이스에서 가져온 것입니다. 이 첫 번째 옵션을 사용하여 페이지를 처리하려면 subscribe
메서드에 Subscriber
인스턴스를 전달하세요. 다음 예제는 subscribe
메서드 사용을 보여 줍니다.
두 번째 subscribe
방법은 SdkPublishersubscribe
의 이 버전에서는 Subscriber
가 아닌 Consumer
subscribe
메서드 변형은 다음 두 번째 예제에 나와 있습니다.
다음 예제는 이전 예제와 동일한 필터 표현식을 사용하는 scan
메서드의 비동기 버전을 보여줍니다.
주석 줄 3번 다음에 DynamoDbAsyncTable.scan
는 PagePublisher
객체를 반환합니다. 다음 줄에서 코드는 org.reactivestreams.Subscriber
인터페이스의 인스턴스를 만들고, ProductCatalogSubscriber
는 주석 줄 4 뒤 PagePublisher
를 구독합니다.
Subscriber
객체는 ProductCatalogSubscriber
클래스 예제의 주석 줄 8 다음에 있는 onNext
메서드의 각 페이지에서 ProductCatalog
항목을 수집합니다. 항목은 전용 List
변수에 저장되며 ProductCatalogSubscriber.getSubscribedItems()
메서드를 사용하여 호출 코드에서 액세스할 수 있습니다. 이는 주석 줄 5 이후에 호출됩니다.
목록이 검색되면 코드는 모든 ProductCatalog
항목을 가격별로 정렬하고 각 항목을 기록합니다.
ProductCatalogSubscriber
클래스CountDownLatch
public static void scanAsync(DynamoDbAsyncTable productCatalog) { ScanEnhancedRequest request = ScanEnhancedRequest.builder() .consistentRead(true) .attributesToProject("id", "title", "authors", "price") .filterExpression(Expression.builder() // 1. :min_value and :max_value are placeholders for the values provided by the map .expression("price >= :min_value AND price <= :max_value") // 2. Two values are needed for the expression and each is supplied as a map entry. .expressionValues( Map.of( ":min_value", numberValue(8.00), ":max_value", numberValue(400_000.00))) .build()) .build(); // 3. A PagePublisher object is returned by the scan method. PagePublisher<ProductCatalog> pagePublisher = productCatalog.scan(request); ProductCatalogSubscriber subscriber = new ProductCatalogSubscriber(); // 4. Subscribe the ProductCatalogSubscriber to the PagePublisher. pagePublisher.subscribe(subscriber); // 5. Retrieve all collected ProductCatalog items accumulated by the subscriber. subscriber.getSubscribedItems().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach(item -> logger.info(item.toString())); // 6. Use a Consumer to work through each page. pagePublisher.subscribe(page -> page .items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach(item -> logger.info(item.toString()))) .join(); // If needed, blocks the subscribe() method thread until it is finished processing. // 7. Use a Consumer to work through each ProductCatalog item. pagePublisher.items() .subscribe(product -> logger.info(product.toString())) .exceptionally(failure -> { logger.error("ERROR - ", failure); return null; }) .join(); // If needed, blocks the subscribe() method thread until it is finished processing. }
private static class ProductCatalogSubscriber implements Subscriber<Page<ProductCatalog>> { private CountDownLatch latch = new CountDownLatch(1); private Subscription subscription; private List<ProductCatalog> itemsFromAllPages = new ArrayList<>(); @Override public void onSubscribe(Subscription sub) { subscription = sub; subscription.request(1L); try { latch.await(); // Called by main thread blocking it until latch is released. } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void onNext(Page<ProductCatalog> productCatalogPage) { // 8. Collect all the ProductCatalog instances in the page, then ask the publisher for one more page. itemsFromAllPages.addAll(productCatalogPage.items()); subscription.request(1L); } @Override public void onError(Throwable throwable) { } @Override public void onComplete() { latch.countDown(); // Call by subscription thread; latch releases. } List<ProductCatalog> getSubscribedItems() { return this.itemsFromAllPages; } }
다음 코드 조각 예제는 주석 줄 6을 다음에 Consumer
를 받아들이는 PagePublisher.subscribe
메서드의 버전을 사용합니다. Java 람다 매개변수는 각 항목을 추가로 처리하는 페이지를 사용합니다. 이 예에서는 각 페이지가 처리되고 각 페이지의 항목이 정렬된 후 기록됩니다.
// 6. Use a Consumer to work through each page. pagePublisher.subscribe(page -> page .items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach(item -> logger.info(item.toString()))) .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
PagePublisher
의 items
메서드는 모델 인스턴스를 언래핑하여 코드가 항목을 직접 처리할 수 있도록 합니다. 이 접근 방법은 다음 코드 조각에 나와 있습니다.
// 7. Use a Consumer to work through each ProductCatalog item. pagePublisher.items() .subscribe(product -> logger.info(product.toString())) .exceptionally(failure -> { logger.error("ERROR - ", failure); return null; }) .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
테이블 쿼리
DynamoDbTable
클래스의 query()
@DynamoDbPartitionKey
주석과 선택적 @DynamoDbSortKey
주석은 데이터 클래스의 기본 키를 정의하는 데 사용됩니다.
query()
메서드에는 제공된 값과 일치하는 항목을 찾는 파티션 키 값이 필요합니다. 테이블에서 정렬 키도 정의하는 경우 추가 비교 조건으로 해당 값을 쿼리에 추가하여 결과를 세부적으로 조정할 수 있습니다.
결과 처리를 제외하고 query()
의 동기 버전과 비동기 버전은 동일하게 작동합니다. 와 마찬가지로 scan
는 동기 호출의 PageIterable
경우 를 API반환하고 비동기 호출PagePublisher
의 경우 를 query
API 반환합니다. 이전에는 스캔 단원에서 PageIterable
및 PagePublisher
의 사용에 대해 설명했습니다.
Query
메서드 예제
다음 query()
메서드 코드 예제에서는 MovieActor
클래스를 사용합니다. 데이터 클래스는 파티션 키의 movie
속성과 정렬 키의 actor
속성으로 구성된 복합 기본 키를 정의합니다.
또한 클래스는 acting_award_year
라는 글로벌 보조 인덱스를 사용한다는 신호를 보냅니다. 데이터 클래스는 파티션 키의 actingaward
속성과 정렬 키의 actingyear
속성으로 구성된 복합 기본 키를 정의합니다. 이 항목의 뒷부분에서 인덱스를 만들고 사용하는 방법을 보여줄 때는 acting_award_year
인덱스를 참조하겠습니다.
package org.example.tests.model; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; import java.util.Objects; @DynamoDbBean public class MovieActor implements Comparable<MovieActor> { private String movieName; private String actorName; private String actingAward; private Integer actingYear; private String actingSchoolName; @DynamoDbPartitionKey @DynamoDbAttribute("movie") public String getMovieName() { return movieName; } public void setMovieName(String movieName) { this.movieName = movieName; } @DynamoDbSortKey @DynamoDbAttribute("actor") public String getActorName() { return actorName; } public void setActorName(String actorName) { this.actorName = actorName; } @DynamoDbSecondaryPartitionKey(indexNames = "acting_award_year") @DynamoDbAttribute("actingaward") public String getActingAward() { return actingAward; } public void setActingAward(String actingAward) { this.actingAward = actingAward; } @DynamoDbSecondarySortKey(indexNames = {"acting_award_year", "movie_year"}) @DynamoDbAttribute("actingyear") public Integer getActingYear() { return actingYear; } public void setActingYear(Integer actingYear) { this.actingYear = actingYear; } @DynamoDbAttribute("actingschoolname") public String getActingSchoolName() { return actingSchoolName; } public void setActingSchoolName(String actingSchoolName) { this.actingSchoolName = actingSchoolName; } @Override public String toString() { final StringBuffer sb = new StringBuffer("MovieActor{"); sb.append("movieName='").append(movieName).append('\''); sb.append(", actorName='").append(actorName).append('\''); sb.append(", actingAward='").append(actingAward).append('\''); sb.append(", actingYear=").append(actingYear); sb.append(", actingSchoolName='").append(actingSchoolName).append('\''); sb.append('}'); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MovieActor that = (MovieActor) o; return Objects.equals(movieName, that.movieName) && Objects.equals(actorName, that.actorName) && Objects.equals(actingAward, that.actingAward) && Objects.equals(actingYear, that.actingYear) && Objects.equals(actingSchoolName, that.actingSchoolName); } @Override public int hashCode() { return Objects.hash(movieName, actorName, actingAward, actingYear, actingSchoolName); } @Override public int compareTo(MovieActor o) { if (this.movieName.compareTo(o.movieName) != 0){ return this.movieName.compareTo(o.movieName); } else { return this.actorName.compareTo(o.actorName); } } }
다음 항목에 대한 쿼리를 따르는 코드 예제.
MovieActor{movieName='movie01', actorName='actor0', actingAward='actingaward0', actingYear=2001, actingSchoolName='null'} MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'} MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'} MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'} MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'} MovieActor{movieName='movie02', actorName='actor0', actingAward='actingaward0', actingYear=2002, actingSchoolName='null'} MovieActor{movieName='movie02', actorName='actor1', actingAward='actingaward1', actingYear=2002, actingSchoolName='actingschool1'} MovieActor{movieName='movie02', actorName='actor2', actingAward='actingaward2', actingYear=2002, actingSchoolName='actingschool2'} MovieActor{movieName='movie02', actorName='actor3', actingAward='actingaward3', actingYear=2002, actingSchoolName='null'} MovieActor{movieName='movie02', actorName='actor4', actingAward='actingaward4', actingYear=2002, actingSchoolName='actingschool4'} MovieActor{movieName='movie03', actorName='actor0', actingAward='actingaward0', actingYear=2003, actingSchoolName='null'} MovieActor{movieName='movie03', actorName='actor1', actingAward='actingaward1', actingYear=2003, actingSchoolName='actingschool1'} MovieActor{movieName='movie03', actorName='actor2', actingAward='actingaward2', actingYear=2003, actingSchoolName='actingschool2'} MovieActor{movieName='movie03', actorName='actor3', actingAward='actingaward3', actingYear=2003, actingSchoolName='null'} MovieActor{movieName='movie03', actorName='actor4', actingAward='actingaward4', actingYear=2003, actingSchoolName='actingschool4'}
다음 코드는 두 QueryConditionalQueryConditionals
작업하며 DynamoDB 서비스의 키 조건식에 해당합니다API. 이 예제는 주석 줄 1 다음에 파티션 값이 movie01
인 항목과 일치하는 keyEqual
인스턴스를 정의합니다.
또한 이 예제는 주석 줄 2 뒤에 actingschoolname
이 없는 항목을 필터링하는 필터 표현식을 정의합니다.
주석 줄 3 다음에 예제는 코드가 DynamoDbTable.query()
메서드로 전달되는 QueryEnhancedRequest
public static void query(DynamoDbTable movieActorTable) { // 1. Define a QueryConditional instance to return items matching a partition value. QueryConditional keyEqual = QueryConditional.keyEqualTo(b -> b.partitionValue("movie01")); // 1a. Define a QueryConditional that adds a sort key criteria to the partition value criteria. QueryConditional sortGreaterThanOrEqualTo = QueryConditional.sortGreaterThanOrEqualTo(b -> b.partitionValue("movie01").sortValue("actor2")); // 2. Define a filter expression that filters out items whose attribute value is null. final Expression filterOutNoActingschoolname = Expression.builder().expression("attribute_exists(actingschoolname)").build(); // 3. Build the query request. QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(keyEqual) .filterExpression(filterOutNoActingschoolname) .build(); // 4. Perform the query. PageIterable<MovieActor> pagedResults = movieActorTable.query(tableQuery); logger.info("page count: {}", pagedResults.stream().count()); // Log number of pages. pagedResults.items().stream() .sorted() .forEach( item -> logger.info(item.toString()) // Log the sorted list of items. );
다음은 메서드 실행 결과입니다. 출력에는 movie01의 movieName
값이 있는 항목이 표시되고 null
과 같은 actingSchoolName
이 있는 항목은 표시되지 않습니다.
2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:46 - page count: 1 2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'} 2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'} 2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
이전에 주석 줄 3 다음에 표시된 다음 쿼리 요청 변형에서는 코드가 keyEqual
QueryConditional
를 주석 줄 1a 뒤에 정의된 sortGreaterThanOrEqualTo
QueryConditional
로 대체합니다. 다음 코드는 또한 필터 표현식을 제거합니다.
QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(sortGreaterThanOrEqualTo)
이 테이블에는 복합 기본 키가 있으므로 모든 QueryConditional
인스턴스에는 파티션 키 값이 필요합니다. sort...
로 시작하는 QueryConditional
메서드는 정 렬 키가 필요함을 나타냅니다. 결과는 정렬되지 않습니다.
다음 출력은 쿼리 결과를 표시합니다. 쿼리는 movieName
값이 movie01과 같은 항목을 반환하고 actor2보다 크거나 같은 actorName
값을 가진 항목만 반환합니다. 필터가 제거되었으므로 쿼리는 actingSchoolName
속성 값이 없는 항목을 반환합니다.
2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:46 - page count: 1 2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'} 2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'} 2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}