From a1eab68f2a847a400bfdd09fe7ff908cfdaea56f Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 9 May 2024 12:12:04 +0200 Subject: [PATCH 1/3] Guide for the Cursor-based pagination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guide for the Cursor-based pagination support added to Micronaut Data JDBC by this [PR #2884](https://github.com/micronaut-projects/micronaut-data/pull/2884) We cannot merge this PR until the framework 4.5.0 release which will contain a Micronaut Data release with that feature. You can generate the guideā€™s HTML locally with : ``` ./gradlew build open build/dist/micronaut-data-cursored-page-gradle-java.html ``` To run the sample code locally, publish to maven local Micronaut Data PR and use: ``` annotationProcessor("io.micronaut.data:micronaut-data-processor:4.8.0-SNAPSHOT") implementation("io.micronaut.data:micronaut-data-jdbc:4.8.0-SNAPSHOT") ``` --- .../main/java/example/micronaut/Fruit.java | 29 ++++++ .../example/micronaut/FruitController.java | 47 ++++++++++ .../example/micronaut/FruitRepository.java | 27 ++++++ .../src/main/resources/application.properties | 9 ++ .../micronaut/FruitControllerTest.java | 62 +++++++++++++ .../metadata.json | 15 +++ .../micronaut-data-cursored-page.adoc | 92 +++++++++++++++++++ 7 files changed, 281 insertions(+) create mode 100644 guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/Fruit.java create mode 100644 guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java create mode 100644 guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java create mode 100644 guides/micronaut-data-cursored-page/java/src/main/resources/application.properties create mode 100644 guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java create mode 100644 guides/micronaut-data-cursored-page/metadata.json create mode 100644 guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc diff --git a/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/Fruit.java b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/Fruit.java new file mode 100644 index 0000000000..d792c748a8 --- /dev/null +++ b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/Fruit.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.micronaut; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable // <1> +@MappedEntity // <2> +public record Fruit(@Id // <3> + @GeneratedValue // <4> + Long id, + String name) { +} \ No newline at end of file diff --git a/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java new file mode 100644 index 0000000000..ed93321c69 --- /dev/null +++ b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.micronaut; + +import io.micronaut.data.model.CursoredPage; +import io.micronaut.data.model.CursoredPageable; +import io.micronaut.data.model.Sort; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; + +import java.util.ArrayList; +import java.util.List; + +@Controller("/fruits") // <1> +class FruitController { + private static final Sort SORT = Sort.of(Sort.Order.asc("name")); + + private final FruitRepository repository; + + FruitController(FruitRepository repository) { // <2> + this.repository = repository; + } + + @Get // <3> + List index() { + CursoredPage page = repository.find(CursoredPageable.from(2, SORT)); // <4> + List fruits = new ArrayList<>(page.getContent()); + while (page.hasNext()) { + page = repository.find(page.nextPageable()); + fruits.addAll(page.getContent()); + } + return fruits; + } +} \ No newline at end of file diff --git a/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java new file mode 100644 index 0000000000..fef81825a9 --- /dev/null +++ b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.micronaut; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.CursoredPage; +import io.micronaut.data.model.CursoredPageable; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@JdbcRepository(dialect = Dialect.H2) // <1> +public interface FruitRepository extends CrudRepository { // <2> + CursoredPage find(CursoredPageable pageable); // <3> +} \ No newline at end of file diff --git a/guides/micronaut-data-cursored-page/java/src/main/resources/application.properties b/guides/micronaut-data-cursored-page/java/src/main/resources/application.properties new file mode 100644 index 0000000000..24f0e792f7 --- /dev/null +++ b/guides/micronaut-data-cursored-page/java/src/main/resources/application.properties @@ -0,0 +1,9 @@ +micronaut.application.name=micronautguide +#tag::datasource[] +datasources.default.password= +datasources.default.dialect=H2 +datasources.default.schema-generate=CREATE_DROP +datasources.default.url=jdbc\:h2\:mem\:devDb;LOCK_TIMEOUT\=10000;DB_CLOSE_ON_EXIT\=FALSE +datasources.default.username=sa +datasources.default.driver-class-name=org.h2.Driver +#end::datasource[] \ No newline at end of file diff --git a/guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java b/guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java new file mode 100644 index 0000000000..af37ff8388 --- /dev/null +++ b/guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.micronaut; + +import io.micronaut.core.type.Argument; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@MicronautTest(transactional = false) +class FruitControllerTest { + + @Test + void itIsPossibleToNavigateWithCursoredPage(@Client("/") HttpClient httpClient, + FruitRepository repository) { + List data = List.of( + new Fruit(null, "apple"), + new Fruit(null, "banana"), + new Fruit(null, "cherry"), + new Fruit(null, "date"), + new Fruit(null, "elderberry"), + new Fruit(null, "fig"), + new Fruit(null, "grape"), + new Fruit(null, "honeydew"), + new Fruit(null, "kiwi"), + new Fruit(null, "lemon") + ); + repository.saveAll(data); + int numberOfFruits = data.size(); + assertEquals(numberOfFruits, repository.count()); + + BlockingHttpClient client = httpClient.toBlocking(); + HttpResponse> response = assertDoesNotThrow(() -> + client.exchange(HttpRequest.GET("/fruits"), (Argument.listOf(Fruit.class)))); + assertEquals(HttpStatus.OK, response.getStatus()); + List fruits = response.body(); + assertEquals(numberOfFruits, fruits.size()); + repository.deleteAll(); + } +} diff --git a/guides/micronaut-data-cursored-page/metadata.json b/guides/micronaut-data-cursored-page/metadata.json new file mode 100644 index 0000000000..a0058aae0d --- /dev/null +++ b/guides/micronaut-data-cursored-page/metadata.json @@ -0,0 +1,15 @@ +{ + "title": "Cursor-based pagination", + "intro": "Learn how to use Cursor-based pagination with Micronaut Data JDBC.", + "authors": ["Sergio del Amo"], + "tags": [], + "categories": ["Micronaut Data"], + "publicationDate": "2024-05-09", + "languages": ["java"], + "apps": [ + { + "name": "default", + "features": ["data-jdbc", "h2"] + } + ] +} diff --git a/guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc b/guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc new file mode 100644 index 0000000000..e6bc2c4776 --- /dev/null +++ b/guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc @@ -0,0 +1,92 @@ +common:header.adoc[] + +This guide uses Micronaut Data's https://micronaut-projects.github.io/micronaut-data/latest/guide/#cursored-pagination[Cursored Pagination] to traverse data. + +Micronaut Data Cursor-page pagination is inspired by https://jakarta.ee/specifications/data/1.0/data-1.0.0-rc1#_cursor_based_pagination[Jakarta Data's Cursor-based pagination]. +____ +Cursor-based pagination aims to reduce missed and duplicate results across pages by querying relative to the observed values of entity properties that constitute the sorting criteria +____ + +common:requirements.adoc[] + +common:completesolution.adoc[] + +common:create-app.adoc[] + +== Writing the Application + +We will create a Micronaut Data JDBC with an H2 database application that exposes a REST API. + +=== Data Source Dependencies + +Add the following dependencies: + +:dependencies: + +dependency:micronaut-data-processor[groupId=io.micronaut.data,scope=annotationProcessor] +dependency:micronaut-data-jdbc[groupId=io.micronaut.data] +dependency:micronaut-jdbc-hikari[groupId=io.micronaut.sql] +dependency:h2[groupId=com.h2database,scope=runtimeOnly,callout=4] + +:dependencies: + +<1> Configures Hibernate/JPA EntityManagerFactory beans. +<2> Adds Micronaut Data Transaction Hibernate dependency. +<3> Configures SQL DataSource instances using Hikari Connection Pool. +<4> Add dependency to in-memory H2 Database. + +:dependencies: + +And the database configuration: + +resource:application.properties[tag=datasource] + +== Writing the Application + +=== Entities + +Add the following entity: + +source:Fruit[] + +callout:serdeable[1] +callout:mapped-entity[2] +callout:mapped-entity-id[3] +callout:generated-value[4] + +=== Repository + +Add a repository interface. Micronaut Data provides an implementation of the interface at compilation time. You can use https://micronaut-projects.github.io/micronaut-data/latest/api/io/micronaut/data/model/CursoredPageable.html[CursoredPageable] as a method parameter and https://micronaut-projects.github.io/micronaut-data/latest/api/io/micronaut/data/model/CursoredPage.html[CursoredPage] as a return type. + + +source:FruitRepository[] + +callout:jdbcrepository[1] +callout:crudrepository[2] +<3> The signature defines a https://micronaut-projects.github.io/micronaut-data/latest/api/io/micronaut/data/model/CursoredPageable.html[CursoredPageable] parameter and https://micronaut-projects.github.io/micronaut-data/latest/api/io/micronaut/data/model/CursoredPage.html[CursoredPage] return type. + +=== Controller + +Create a controller that uses the repository's method with Cursor Pagination capabilities. + +source:FruitController[] + +callout:controller[number=1,arg0=/fruits] +callout:constructor-di[number=2,arg0=FruitRepository] +callout:get-generic[number=3] +<4> The code uses `CursoredPage::hasNext` and `CursorPage::nextPageable` to traverse the data. + +=== Tests + +The following test verifies the controller fetches every entity. + +test:FruitControllerTest[] + +callout:micronaut-test-transactional-false[1] +callout:http-client[2] + +== Next steps + +Read more about https://micronaut-projects.github.io/micronaut-data/latest/guide/#cursored-pagination[Micronaut Data's Cursored Pagination] support. + +common:helpWithMicronaut.adoc[] \ No newline at end of file From 118debd1ca1988e8699cdc35bba703f4f577d3fa Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Thu, 9 May 2024 12:43:14 +0100 Subject: [PATCH 2/3] Fix calllouts --- .../test/java/example/micronaut/FruitControllerTest.java | 4 ++-- .../micronaut-data-cursored-page.adoc | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java b/guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java index af37ff8388..5d62a8fd0d 100644 --- a/guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java +++ b/guides/micronaut-data-cursored-page/java/src/test/java/example/micronaut/FruitControllerTest.java @@ -29,11 +29,11 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -@MicronautTest(transactional = false) +@MicronautTest(transactional = false) // <1> class FruitControllerTest { @Test - void itIsPossibleToNavigateWithCursoredPage(@Client("/") HttpClient httpClient, + void itIsPossibleToNavigateWithCursoredPage(@Client("/") HttpClient httpClient, // <2> FruitRepository repository) { List data = List.of( new Fruit(null, "apple"), diff --git a/guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc b/guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc index e6bc2c4776..58d2e5955b 100644 --- a/guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc +++ b/guides/micronaut-data-cursored-page/micronaut-data-cursored-page.adoc @@ -23,9 +23,9 @@ Add the following dependencies: :dependencies: -dependency:micronaut-data-processor[groupId=io.micronaut.data,scope=annotationProcessor] -dependency:micronaut-data-jdbc[groupId=io.micronaut.data] -dependency:micronaut-jdbc-hikari[groupId=io.micronaut.sql] +dependency:micronaut-data-processor[groupId=io.micronaut.data,scope=annotationProcessor,callout=1] +dependency:micronaut-data-jdbc[groupId=io.micronaut.data,callout=2] +dependency:micronaut-jdbc-hikari[groupId=io.micronaut.sql,callout=3] dependency:h2[groupId=com.h2database,scope=runtimeOnly,callout=4] :dependencies: From 68b5223ffdb78d8795df4f7b8b3d08b319193601 Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Thu, 9 May 2024 12:43:23 +0100 Subject: [PATCH 3/3] Whitespace --- .../java/src/main/java/example/micronaut/FruitController.java | 1 + .../java/src/main/java/example/micronaut/FruitRepository.java | 1 + 2 files changed, 2 insertions(+) diff --git a/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java index ed93321c69..1899649c56 100644 --- a/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java +++ b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitController.java @@ -26,6 +26,7 @@ @Controller("/fruits") // <1> class FruitController { + private static final Sort SORT = Sort.of(Sort.Order.asc("name")); private final FruitRepository repository; diff --git a/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java index fef81825a9..cf9435dfe1 100644 --- a/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java +++ b/guides/micronaut-data-cursored-page/java/src/main/java/example/micronaut/FruitRepository.java @@ -23,5 +23,6 @@ @JdbcRepository(dialect = Dialect.H2) // <1> public interface FruitRepository extends CrudRepository { // <2> + CursoredPage find(CursoredPageable pageable); // <3> } \ No newline at end of file