You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus中Hibernate Reactive与Panache的findById返回脏数据问题

Quarkus+Hibernate Reactive+Panache Reactive更新实体的脏数据问题解析

在使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:03:11