Quarkus Reactive+Hibernate Reactive触发Illegal pop()异常
问题分析与解决方案
核心原因
Illegal pop() with non-matching JdbcValuesSourceProcessingState异常本质是Hibernate Reactive会话上下文冲突导致的。Hibernate Reactive的Session并非线程安全,当你用Uni.combine().all()并行执行多个数据库查询时,多个操作会共享同一个上下文会话,导致会话内部的状态栈被非法修改。
修复方案
你需要确保每个数据库查询操作都运行在独立的会话上下文中,或者通过显式会话管理隔离并行操作。以下是两种可行的修复方式:
方式1:为每个查询显式绑定独立会话
用SessionFactory.withSession()包裹每个数据库操作,确保并行查询各自使用独立会话:
@Inject SessionFactory sessionFactory; @GET @Path("/availabilities") public Uni<List<VehicleAvailability>> vehicleAvailabilitiesForRide(@QueryParam("rideId") String rideId) { return sessionFactory.withSession(session -> session.find(PlannedRide.class, rideId) .map(ride -> { if (ride == null) { throw new NotFoundException(); } return ride; }) ).flatMap(ride -> { // 每个并行查询绑定独立会话 Uni<List<VehicleReservation>> reservationsUni = sessionFactory.withSession(session -> VehicleReservation.findReservationsInPeriod(ride.getStart(), ride.getEnd()) ); Uni<List<PlannedRide>> plannedRidesUni = sessionFactory.withSession(session -> PlannedRide.findRidesInPeriod(ride.getStart(), ride.getEnd()) ); return Uni.combine().all().unis(reservationsUni, plannedRidesUni).asTuple(); }).flatMap(tuple -> { PlannedRide ride = tuple.getItem1(); List<VehicleReservation> reservations = tuple.getItem2(); List<PlannedRide> plannedRides = tuple.getItem3(); // 车辆查询同样使用独立会话 return sessionFactory.withSession(session -> session.createQuery("from Vehicle", Vehicle.class).list() ).map(vehicles -> vehicles.stream().map(vehicle -> { if (vehicle.isDefective()) { return new VehicleAvailability(vehicle, Availability.DEFECTIVE); } // 检查预约状态 for (VehicleReservation reservation : reservations) { if (Objects.equals(reservation.getVehicle().getId(), vehicle.getId())) { if (Objects.equals(reservation.getPlannedRide().getId(), ride.getId())) { return new VehicleAvailability(vehicle, Availability.RESERVED); } else { return new VehicleAvailability(vehicle, Availability.NOT_AVAILABLE); } } } // 检查重叠行程 boolean hasOverlappingOtherRides = plannedRides.stream() .anyMatch(iterRide -> !Objects.equals(ride.getId(), iterRide.getId())); if (hasOverlappingOtherRides) { return new VehicleAvailability(vehicle, Availability.POTENTIALLY_AVAILABLE); } return new VehicleAvailability(vehicle, Availability.AVAILABLE); }).toList()); }); }
方式2:调整执行顺序避免并行冲突
如果显式会话管理仍有问题,可以调整查询顺序,放弃部分并行性以避免上下文冲突:
@GET @Path("/availabilities") public Uni<List<VehicleAvailability>> vehicleAvailabilitiesForRide(@QueryParam("rideId") String rideId) { return PlannedRide.<PlannedRide>findById(rideId) .map(ride -> { if (ride == null) { throw new NotFoundException(); } return ride; }) // 先获取预约数据,再获取行程数据 .flatMap(ride -> VehicleReservation.findReservationsInPeriod(ride.getStart(), ride.getEnd()) .map(reservations -> Tuple.of(ride, reservations)) ) .flatMap(tuple -> { PlannedRide ride = tuple.getItem1(); List<VehicleReservation> reservations = tuple.getItem2(); return PlannedRide.findRidesInPeriod(ride.getStart(), ride.getEnd()) .map(plannedRides -> Tuple.of(ride, reservations, plannedRides)); }) .flatMap(tuple -> { PlannedRide ride = tuple.getItem1(); List<VehicleReservation> reservations = tuple.getItem2(); List<PlannedRide> plannedRides = tuple.getItem3(); return Vehicle.findAll().list() .map(vehicles -> vehicles.stream().map(vehicle -> { // 状态判断逻辑保持不变 if (vehicle.isDefective()) { return new VehicleAvailability(vehicle, Availability.DEFECTIVE); } for (VehicleReservation reservation : reservations) { if (Objects.equals(reservation.getVehicle().getId(), vehicle.getId())) { if (Objects.equals(reservation.getPlannedRide().getId(), ride.getId())) { return new VehicleAvailability(vehicle, Availability.RESERVED); } else { return new VehicleAvailability(vehicle, Availability.NOT_AVAILABLE); } } } boolean hasOverlappingOtherRides = plannedRides.stream() .anyMatch(iterRide -> !Objects.equals(ride.getId(), iterRide.getId())); if (hasOverlappingOtherRides) { return new VehicleAvailability(vehicle, Availability.POTENTIALLY_AVAILABLE); } return new VehicleAvailability(vehicle, Availability.AVAILABLE); }).toList()); }); }
额外注意事项
- 检查
findReservationsInPeriod和findRidesInPeriod的实现,确保它们依赖当前上下文会话执行查询(比如用Session.createQuery而非硬编码的静态会话)。 - 避免在并行Uni操作中访问实体的懒加载属性,这会进一步加剧会话上下文冲突。
内容的提问来源于stack exchange,提问作者Patrick Lindner
相关产品推荐
相关产品推荐

