Bekerja dengan hasil paginasi: pemindaian dan kueri - AWS SDK for Java 2.x

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

Bekerja dengan hasil paginasi: pemindaian dan kueri

Metodescan, query dan batch metode DynamoDB Enhanced API Client mengembalikan respons dengan satu halaman atau lebih. Sebuah halaman berisi satu atau lebih item. Kode Anda dapat memproses respons per halaman atau dapat memproses item individual.

Respons paginasi yang dikembalikan oleh DynamoDbEnhancedClient klien sinkron mengembalikan PageIterableobjek, sedangkan respons yang dikembalikan oleh DynamoDbEnhancedAsyncClient asinkron mengembalikan objek. PagePublisher

Bagian ini melihat pemrosesan hasil paginasi dan memberikan contoh yang menggunakan pemindaian dan kueriAPIs.

Memindai tabel

scanMetode SDK ini sesuai dengan operasi DynamoDB dengan nama yang sama. DynamoDB Enhanced API Client menawarkan opsi yang sama tetapi menggunakan model objek yang sudah dikenal dan menangani pagination untuk Anda.

Pertama, kita menjelajahi PageIterable antarmuka dengan melihat scan metode kelas pemetaan sinkron,. DynamoDbTable

Gunakan sinkron API

Contoh berikut menunjukkan scan metode yang menggunakan ekspresi untuk memfilter item yang dikembalikan. ProductCatalogIni adalah objek model yang ditunjukkan sebelumnya.

Ekspresi pemfilteran yang ditampilkan setelah baris komentar 2 membatasi ProductCatalog item yang dikembalikan ke item dengan nilai harga antara 8,00 dan 80,00 secara inklusif.

Contoh ini juga mengecualikan isbn nilai dengan menggunakan attributesToProject metode yang ditampilkan setelah baris komentar 1.

Setelah komentar baris 3, PageIterable objekpagedResults,, dikembalikan oleh scan metode. streamMetode PageIterable mengembalikan java.util.Streamobjek, yang dapat Anda gunakan untuk memproses halaman. Dalam contoh ini, jumlah halaman dihitung dan dicatat.

Dimulai dengan baris komentar 4, contoh menunjukkan dua variasi mengakses ProductCatalog item. Versi setelah baris komentar 4a mengalir melalui setiap halaman dan mengurutkan dan mencatat item di setiap halaman. Versi setelah baris komentar 4b melewatkan iterasi halaman dan mengakses item secara langsung.

PageIterableAntarmuka menawarkan beberapa cara untuk memproses hasil karena dua antarmuka induknya— java.lang.Iterabledan SdkIterable. IterablemembawaforEach, iterator dan spliterator metode, dan SdkIterable membawa stream metode.

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

Gunakan asinkron API

scanMetode asinkron mengembalikan hasil sebagai objek. PagePublisher PagePublisherAntarmuka memiliki dua subscribe metode yang dapat Anda gunakan untuk memproses halaman respons. Salah satu subscribe metode berasal dari antarmuka org.reactivestreams.Publisher induk. Untuk memproses halaman menggunakan opsi pertama ini, berikan Subscriber instance ke subscribe metode. Contoh pertama yang berikut menunjukkan penggunaan subscribe metode.

subscribeMetode kedua berasal dari SdkPublisherantarmuka. Versi ini subscribe menerima Consumerbukan aSubscriber. Variasi subscribe metode ini ditunjukkan pada contoh kedua berikut.

Contoh berikut menunjukkan versi asinkron dari scan metode yang menggunakan ekspresi filter yang sama yang ditunjukkan pada contoh sebelumnya.

Setelah baris komentar 3, DynamoDbAsyncTable.scan mengembalikan PagePublisher objek. Pada baris berikutnya, kode membuat instance org.reactivestreams.Subscriber antarmuka,ProductCatalogSubscriber, yang berlangganan baris PagePublisher setelah komentar 4.

SubscriberObjek mengumpulkan ProductCatalog item dari setiap halaman dalam onNext metode setelah baris komentar 8 dalam contoh ProductCatalogSubscriber kelas. Item disimpan dalam List variabel pribadi dan diakses dalam kode panggilan dengan ProductCatalogSubscriber.getSubscribedItems() metode. Ini disebut setelah baris komentar 5.

Setelah daftar diambil, kode mengurutkan semua ProductCatalog item berdasarkan harga dan mencatat setiap item.

CountDownLatchDi ProductCatalogSubscriber kelas memblokir thread panggilan sampai semua item telah ditambahkan ke daftar sebelum melanjutkan setelah baris komentar 5.

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

Contoh cuplikan berikut menggunakan versi PagePublisher.subscribe metode yang menerima baris Consumer setelah komentar 6. Parameter lambda Java mengkonsumsi halaman, yang selanjutnya memproses setiap item. Dalam contoh ini, setiap halaman diproses dan item pada setiap halaman diurutkan dan kemudian dicatat.

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

itemsMetode PagePublisher membuka bungkus contoh model sehingga kode Anda dapat memproses item secara langsung. Pendekatan ini ditunjukkan dalam cuplikan berikut.

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

Mengkueri Tabel

query()Metode DynamoDbTable kelas menemukan item berdasarkan nilai kunci primer. @DynamoDbPartitionKeyAnotasi dan @DynamoDbSortKey anotasi opsional digunakan untuk menentukan kunci utama pada kelas data Anda.

query()Metode ini membutuhkan nilai kunci partisi yang menemukan item yang cocok dengan nilai yang diberikan. Jika tabel Anda juga mendefinisikan kunci pengurutan, Anda dapat menambahkan nilai untuk itu ke kueri Anda sebagai kondisi perbandingan tambahan untuk menyempurnakan hasilnya.

Kecuali untuk memproses hasil, versi sinkron dan asinkron bekerja sama. query() Seperti halnya scanAPI, query API mengembalikan a PageIterable untuk panggilan sinkron dan panggilan PagePublisher untuk asinkron. Kami membahas penggunaan PageIterable dan PagePublisher sebelumnya di bagian pemindaian.

Querycontoh metode

Contoh kode query() metode yang mengikuti menggunakan MovieActor kelas. Kelas data mendefinisikan kunci primer komposit yang terdiri dari movieatribut untuk kunci partisi dan actoratribut untuk kunci sortir.

Kelas ini juga memberi sinyal bahwa ia menggunakan indeks sekunder global bernama acting_award_year. Kunci primer komposit indeks terdiri dari actingawardatribut untuk kunci partisi dan actingyearuntuk kunci sortir. Kemudian dalam topik ini, ketika kita menunjukkan cara membuat dan menggunakan indeks, kita akan merujuk ke acting_award_yearindeks.

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

Contoh kode yang mengikuti kueri terhadap item berikut.

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

Kode berikut mendefinisikan dua QueryConditionalcontoh. QueryConditionalsbekerja dengan nilai kunci — baik kunci partisi saja atau dalam kombinasi dengan kunci sortir — dan sesuai dengan ekspresi kondisional kunci dari layanan DynamoDB. API Setelah baris komentar 1, contoh mendefinisikan keyEqual instance yang cocok dengan item dengan nilai partisi. movie01

Contoh ini juga mendefinisikan ekspresi filter yang memfilter item apa pun yang tidak actingschoolnameaktif setelah baris komentar 2.

Setelah baris komentar 3, contoh menunjukkan QueryEnhancedRequestcontoh bahwa kode diteruskan ke DynamoDbTable.query() metode. Objek ini menggabungkan kondisi kunci dan filter yang SDK digunakan untuk menghasilkan permintaan ke layanan DynamoDB.

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. );

Berikut ini adalah output dari menjalankan metode. Output menampilkan item dengan movieName nilai movie01 dan tidak menampilkan item yang actingSchoolName sama dengan. null

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

Dalam variasi permintaan kueri berikut yang ditampilkan sebelumnya setelah baris komentar 3, kode menggantikan keyEqual QueryConditional dengan sortGreaterThanOrEqualTo QueryConditional yang didefinisikan setelah baris komentar 1a. Kode berikut juga menghapus ekspresi filter.

QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(sortGreaterThanOrEqualTo)

Karena tabel ini memiliki kunci primer komposit, semua QueryConditional instance memerlukan nilai kunci partisi. QueryConditionalmetode yang dimulai dengan sort... menunjukkan bahwa kunci pengurutan diperlukan. Hasilnya tidak diurutkan.

Output berikut menampilkan hasil dari query. Kueri mengembalikan item yang memiliki movieName nilai sama dengan movie01 dan hanya item yang memiliki actorName nilai yang lebih besar dari atau sama dengan actor2. Karena filter telah dihapus, kueri mengembalikan item yang tidak memiliki nilai untuk actingSchoolName atribut.

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