Quarkus中Hibernate Reactive与Panache的findById返回脏数据问题
在使用Quarkus、Hibernate Reactive和Panache Reactive开发时,我遇到了数据库实体更新的异常行为——三个修改Workplace实例的方法表现完全不同:
方法一:更新后直接查询,返回最新数据
@WithTransaction public Uni<Workplace> modifyWorkplace(Workplace request) { return workplaceRepository.update("nickname = ?1, version = ?2 where id = ?3", request.getNickname(), request.getVersion(), request.getId()) .onItem().transformToUni(updateCount -> { return workplaceRepository.findById(request.getId()); }) .onItem().ifNull().failWith(() -> new IllegalArgumentException("Workplace not found")); }
这个方法里,执行更新语句后立刻调用findById,能正确获取到更新后的字段值。
方法二:先查询再更新,返回脏数据
@WithSession @WithTransaction public Uni<Workplace> modifyWorkplace(Workplace request) { return workplaceRepository.findById(request.getId()) .chain(existing -> workplaceRepository.update("nickname = ?1, version = ?2 where id = ?3", request.getNickname(), request.getVersion(), request.getId()) .chain(updateCount -> { return workplaceRepository.findById(request.getId()); }) ) .onItem().ifNull().failWith(() -> new IllegalArgumentException("Workplace not found")); }
这个方法中,先通过findById加载了Workplace实例,之后执行更新语句,再次调用findById时,始终返回旧的脏数据,而非数据库中更新后的内容。
方法三:使用session.refresh解决问题
@WithSession @WithTransaction public Uni<Workplace> modifyWorkplace(Workplace request) { return workplaceRepository.findById(request.getId()) .onItem() .ifNotNull() .transformToUni(existing -> workplaceRepository.update("nickname = ?1, version = ?2 where id = ?3", request.getNickname(), request.getVersion(), request.getId()) .onItem().transformToUni(updateCount -> { return workplaceRepository.getSession() .onItem() .ifNotNull() .transformToUni(session -> session.refresh(existing).replaceWith(existing)); }) ) .onItem().ifNull().failWith(() -> new IllegalArgumentException("Workplace not found")); }
通过调用session.refresh(existing),成功获取到了更新后的实体数据,解决了脏数据问题。
核心问题解答
1. 为什么第二个方法始终返回脏数据?
这是Hibernate**一级缓存(Session缓存)**的机制导致的:第一次调用findById时,Hibernate会把加载的Workplace实例存入当前Session的一级缓存。后续再查询同一个ID的实体,Hibernate会直接从缓存返回已有实例,不会去数据库查最新数据。
而workplaceRepository.update()执行的是JPQL批量更新语句,这种操作只会修改数据库数据,不会同步更新Session缓存里的实体实例。加上方法二用了@WithSession,整个方法共用同一个Session,缓存的旧实例一直存在,所以第二次findById拿到的是脏数据。
反观方法一没有@WithSession,更新和查询可能处于不同的Session上下文(或Session在更新后被清理),查询时会重新从数据库加载最新数据。
2. 为什么使用session.refresh能解决该问题?
session.refresh(existing)的作用是强制Hibernate从数据库重新加载指定实体的最新数据,并覆盖Session缓存中该实体的旧数据。
方法三中,执行完JPQL更新后调用refresh,会把数据库的最新状态同步到缓存里的existing实例上,之后返回的就是更新后的实体数据,自然解决了脏数据问题。
内容的提问来源于stack exchange,提问作者powerxon

