如何在响应式Quarkus应用中处理分页并解决HR000068错误?
Quarkus响应式分页实现方案(解决HR000068错误)
错误原因分析
HR000068错误本质是Hibernate Reactive的操作必须严格在Vert.x EventLoop线程执行,你合并两个Uni时,大概率是其中某个操作意外切换到了非EventLoop线程(比如误用阻塞操作、手动指定了Worker线程调度),导致会话上下文失效。
核心解决方案
1. 优先利用Panache Reactive原生分页能力
Panache Reactive已经封装了分页和计数的便捷API,直接使用即可避免手动会话管理的坑:
步骤1:定义分页响应体
public class PaginatedResponse<T> { private List<T> data; private long totalElements; private int currentPage; private int pageSize; public PaginatedResponse(List<T> data, long totalElements, int currentPage, int pageSize) { this.data = data; this.totalElements = totalElements; this.currentPage = currentPage; this.pageSize = pageSize; } // 省略getter/setter }
步骤2:Reactive Routes中实现分页接口
import io.quarkus.hibernate.reactive.panache.PanacheQuery; import io.quarkus.hibernate.reactive.panache.common.Page; import io.smallrye.mutiny.Uni; import io.vertx.ext.web.RoutingContext; import io.vertx.core.json.Json; public class EntityResource { public void handlePaginatedRequest(RoutingContext ctx) { // 从请求参数获取分页参数 int page = Integer.parseInt(ctx.request().getParam("page", "0")); int size = Integer.parseInt(ctx.request().getParam("size", "10")); // 1. 构建分页查询 PanacheQuery<YourEntity> query = YourEntity.findAll().page(Page.of(page, size)); // 2. 并行获取数据列表和总条数(均在EventLoop线程执行) Uni<List<YourEntity>> listUni = query.list(); Uni<Long> countUni = YourEntity.count(); // 3. 合并两个Uni并映射成分页响应 Uni.combine().all().unis(listUni, countUni) .asTuple() .map(tuple -> new PaginatedResponse<>( tuple.getItem1(), tuple.getItem2(), page, size )) .subscribe().with( response -> ctx.response() .putHeader("Content-Type", "application/json") .end(Json.encode(response)), error -> ctx.fail(error) ); } }
2. 手动管理会话(复杂场景下)
如果需要自定义查询逻辑,必须确保所有Hibernate操作在同一个会话且EventLoop线程执行,可通过@WithSession注解或Panache.withSession方法实现:
方式1:使用@WithSession注解
import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.smallrye.mutiny.Uni; public class CustomEntityResource { @WithSession // 确保方法内的Hibernate操作在EventLoop线程的会话中执行 public Uni<PaginatedResponse<YourEntity>> getCustomPaginatedData(int page, int size) { PanacheQuery<YourEntity> query = YourEntity.find("status = ?1", "ACTIVE") .page(Page.of(page, size)); return Uni.combine().all().unis(query.list(), query.count()) .asTuple() .map(tuple -> new PaginatedResponse<>( tuple.getItem1(), tuple.getItem2(), page, size )); } }
方式2:手动绑定会话
import io.quarkus.hibernate.reactive.panache.Panache; import io.smallrye.mutiny.Uni; public class CustomEntityResource { public Uni<PaginatedResponse<YourEntity>> getCustomPaginatedData(int page, int size) { return Panache.withSession(session -> { // 在会话内执行所有查询 Uni<List<YourEntity>> listUni = session.createQuery("FROM YourEntity WHERE status = :status", YourEntity.class) .setParameter("status", "ACTIVE") .setFirstResult(page * size) .setMaxResults(size) .getResultList(); Uni<Long> countUni = session.createQuery("SELECT COUNT(*) FROM YourEntity WHERE status = :status", Long.class) .setParameter("status", "ACTIVE") .getSingleResult(); return Uni.combine().all().unis(listUni, countUni) .asTuple() .map(tuple -> new PaginatedResponse<>( tuple.getItem1(), tuple.getItem2(), page, size )); }); } }
关键注意事项
- 绝对禁止阻塞操作:不要在Uni的回调中使用
block()、await()等方法,会强制切换到非EventLoop线程,触发错误。 - 避免手动线程调度:不要随意使用
runSubscriptionOn()或emitOn()指定Worker线程,Hibernate Reactive操作必须在EventLoop线程执行。 - 复用同一会话:复杂查询时,确保列表查询和计数查询在同一个会话内完成,避免会话上下文丢失。
内容的提问来源于stack exchange,提问作者Sergio Rodríguez Calvo
相关产品推荐
相关产品推荐

