Overview
本教程向您展示如何在连接到MongoDB 的Quarkus应用程序中实现分页技术。您学习如何使用 Jakarta 数据存储库创建同时支持基于偏移和基于游标的分页方法的 REST API 终结点。
分页
分页是一种将大型数据集划分为更小、更易于管理的数据块的技术。 本教程实现了基于偏移和基于游标的分页方法。基于偏移量的分页使用页码来检索特定的数据子集,而基于游标的分页使用参考点或游标来浏览数据集。
Tutorial
本教程介绍如何执行以下操作:
验证先决条件
创建具有所需依赖项的 Quarkus项目
配置MongoDB连接
定义数据实体和存储库
实现用于分页的REST API终结点
测试分页终结点
验证先决条件。
在开始之前,请完成以下先决任务:
在MongoDB Atlas或本地Docker实例上配置MongoDB 集群
要使用Docker启动本地MongoDB实例,运行以下命令:
docker run --rm -d --name mongodb-instance -p 27017:27017 mongo或者,您可以使用MongoDB Atlas并部署免费的 M0集群。要学习;了解如何创建Atlas帐户和集群,请参阅MongoDB入门指南。
创建 Quarkus项目。
导航到 Quarkus Code Generator 并使用以下设置配置您的项目:
选择首选群组和工件ID。
添加以下依赖项:
JNoSQL 文档MongoDB (
quarkus-jnosql-document-mongodb)RESTEasy 响应式 (
quarkus-resteasy-reactive)RESTEasy Reactive Jackson (
quarkus-resteasy-reactive-jackson)OpenAPI (
quarkus-smallrye-openapi)
生成项目,下载ZIP文件并将其解压缩。
注意
如果在生成器中找不到依赖项,请将其手动添加到 pom.xml文件中。
完成设置后,请验证您的 pom.xml文件是否包含以下依赖项:
<dependencies> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-openapi</artifactId> </dependency> <dependency> <groupId>io.quarkiverse.jnosql</groupId> <artifactId>quarkus-jnosql-document-mongodb</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive-jackson</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-arc</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-junit5</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <scope>test</scope> </dependency></dependencies>配置MongoDB 数据库。
打开 application.properties文件并添加以下配置属性以连接到MongoDB实例:
quarkus.mongodb.connection-string = <your connection string>jnosql.document.database = fruits此配置使您的应用程序能够通过指定的连接字符串连接到MongoDB 集群并使用 fruits数据库。
重要
在生产环境中,启用访问权限控制并实施身份验证。有关详细信息,请参阅安全检查清单。
您可以使用环境变量覆盖这些属性,这样您就可以为开发、测试和生产指定不同的配置,而无需修改代码。
创建数据实体。
在 src/main/java目录中创建 Fruit 实体类。以下代码定义具有 id 和 name 字段的实体:
import jakarta.nosql.Column;import jakarta.nosql.Convert;import jakarta.nosql.Entity;import jakarta.nosql.Id;import org.eclipse.jnosql.databases.mongodb.mapping.ObjectIdConverter;
@Entitypublic class Fruit {
@Id @Convert(ObjectIdConverter.class) private String id;
@Column private String name;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { return "Fruit{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; }
public static Fruit of(String name) { Fruit fruit = new Fruit(); fruit.setName(name); return fruit; }
}创建存储库接口。
创建用于扩展 BasicRepository 类的 FruitRepository 接口。以下代码定义了基于偏移和基于游标的分页方法:
import jakarta.data.Sort;import jakarta.data.page.CursoredPage;import jakarta.data.page.Page;import jakarta.data.page.PageRequest;import jakarta.data.repository.BasicRepository;import jakarta.data.repository.Find;import jakarta.data.repository.OrderBy;import jakarta.data.repository.Repository;
@Repositorypublic interface FruitRepository extends BasicRepository<Fruit, String> {
@Find CursoredPage<Fruit> cursor(PageRequest pageRequest, Sort<Fruit> order);
@Find @OrderBy("name") Page<Fruit> offSet(PageRequest pageRequest);
long countBy();
}框架会自动实现此接口,使您无需编写样板代码即可执行数据库操作。
创建数据库设置程序类。
在 src/main/java目录中创建一个 SetupDatabase 类。以下代码在初创企业时使用示例数据填充数据库,并在关闭时删除数据:
import jakarta.enterprise.context.ApplicationScoped;import jakarta.enterprise.event.Observes;import io.quarkus.runtime.ShutdownEvent;import io.quarkus.runtime.StartupEvent;import org.jboss.logging.Logger;import java.util.List;
@ApplicationScopedpublic class SetupDatabase {
private static final Logger LOGGER = Logger.getLogger(SetupDatabase.class.getName());
private final FruitRepository fruitRepository;
public SetupDatabase(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; }
void onStart(@Observes StartupEvent ev) { LOGGER.info("The application is starting..."); long count = fruitRepository.countBy(); if (count > 0) { LOGGER.info("Database already populated"); return; } List<Fruit> fruits = List.of( Fruit.of("apple"), Fruit.of("banana"), Fruit.of("cherry"), Fruit.of("date"), Fruit.of("elderberry"), Fruit.of("fig"), Fruit.of("grape"), Fruit.of("honeydew"), Fruit.of("kiwi"), Fruit.of("lemon") ); fruitRepository.saveAll(fruits); }
void onStop(@Observes ShutdownEvent ev) { LOGGER.info("The application is stopping..."); fruitRepository.deleteAll(fruitRepository.findAll().toList()); }
}创建REST API端点。
在 src/main/java目录中创建一个 FruitResource 类。然后,将以下代码粘贴到类文件中:
import jakarta.data.Sort;import jakarta.data.page.PageRequest;import jakarta.ws.rs.DefaultValue;import jakarta.ws.rs.GET;import jakarta.ws.rs.Path;import jakarta.ws.rs.Produces;import jakarta.ws.rs.QueryParam;import jakarta.ws.rs.core.MediaType;
@Path("/fruits")public class FruitResource {
private final FruitRepository fruitRepository;
private static final Sort<Fruit> ASC = Sort.asc("name"); private static final Sort<Fruit> DESC = Sort.desc("name");
public FruitResource(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; }
@Path("/offset") @GET @Produces(MediaType.APPLICATION_JSON) public Iterable<Fruit> offset(@QueryParam("page") @DefaultValue("1") long page, @QueryParam("size") @DefaultValue("2") int size) { var pageRequest = PageRequest.ofPage(page).size(size); return fruitRepository.offSet(pageRequest).content(); }
@Path("/cursor") @GET @Produces(MediaType.APPLICATION_JSON) public Iterable<Fruit> cursor(@QueryParam("after") @DefaultValue("") String after, @QueryParam("before") @DefaultValue("") String before, @QueryParam("size") @DefaultValue("2") int size) { if (!after.isBlank()) { var pageRequest = PageRequest.ofSize(size).afterCursor(PageRequest.Cursor.forKey(after)); return fruitRepository.cursor(pageRequest, ASC).content(); } else if (!before.isBlank()) { var pageRequest = PageRequest.ofSize(size).beforeCursor(PageRequest.Cursor.forKey(before)); return fruitRepository.cursor(pageRequest, DESC).stream().toList(); } var pageRequest = PageRequest.ofSize(size); return fruitRepository.cursor(pageRequest, ASC).content(); }
}该类定义了以下终结点:
/fruits/offset:使用page查询参数支持基于偏移量的分页。/fruits/cursor:使用after和before查询参数支持基于游标的分页。
两个终结点还接受 size查询参数来指定每页的列项数。
测试基于游标的分页。
使用以下 curl 命令测试游标分页端点。这些命令使用 after 和 before 参数浏览数据集。
要获取初始水果设立,运行以下命令:
curl --location http://localhost:8080/fruits/cursor要获取 name字段值在 "banana" 之后的水果,运行以下命令:
curl --location http://localhost:8080/fruits/cursor?after=banana要获取 name字段值在 "date" 之前的水果,运行以下命令:
curl --location http://localhost:8080/fruits/cursor?before=date其他资源
要学习有关MongoDB分页的更多信息,请参阅MongoDB Atlas文档中的分页结果指南。
要学习;了解有关 Quarkus 的更多信息,请参阅Quarkus 文档。