JPA中应使用EntityManager.clear()还是detach()来取消实体变更?
JPA实体变更取消相关问题解答
EntityManager.clear()的影响范围
- EntityManager是事务绑定的,每个事务持有独立的EntityManager实例,不同并发事务的持久化上下文完全隔离,因此
clear()只会清空当前事务内的所有托管实体,不会影响其他并发事务的实体,不会出现跨事务意外分离实体的情况。 - 但在当前事务内部,
clear()会将所有已加载的托管实体全部转为游离状态,所有未刷入数据库的实体变更都会被丢弃,确实会影响同事务内其他不相关的实体,除非你确认当前事务不需要保留任何未提交的变更,否则不建议随意使用clear()。
为什么detach()有时无法阻止变更持久化
常见原因有三类:
- 操作的不是当前上下文的托管实体:如果传入
detach()的对象是前端传入的DTO、已经游离的实体、或是手动复制的新对象,并非和当前持久化上下文关联的托管实例,detach()不会生效。 - 级联配置导致关联实体变更漏处理:如果实体配置了
cascade = CascadeType.PERSIST/MERGE等级联策略,关联实体发生的变更,即使你detach了主实体,关联实体的变更还是会被自动持久化。 - 变更已提前刷新到事务缓冲区:在调用
detach()之前,可能因为执行查询操作、或是EntityManager触发了auto-flush,变更已经写入数据库事务缓冲区,后续事务提交时依然会落地,此时detach无法撤销已经写入缓冲区的变更。
可行解决方案
方案1:正确使用detach()
- 操作前先调用
entityManager.contains(entity)确认目标对象是当前上下文的托管实体,如果不是,先通过entityManager.find()获取当前托管实例再操作。 - 若有关联实体需要同时取消变更,手动对关联的托管实体也调用
detach()。 - 必要时在detach前调用
entityManager.flush(),将其他需要保留的变更先刷入数据库,再处理需要取消变更的实体。
示例代码:
// 确认实体处于托管状态 if (entityManager.contains(toBeCanceledEntity)) { // 若有关联实体需要同步取消变更,提前detach entityManager.detach(toBeCanceledEntity.getRelatedEntity()); entityManager.detach(toBeCanceledEntity); }
方案2:使用refresh()撤销单实体变更
如果只是需要撤销单个实体的所有修改,优先使用entityManager.refresh(entity),该方法会直接用数据库中的最新值覆盖实体的所有临时变更,不会影响其他托管实体,比detach适配性更强。
方案3:只读事务规避变更持久化
如果业务场景只是查询实体做临时修改、不需要持久化任何变更,直接给对应方法标注@Transactional(readOnly = true),JPA会关闭自动持久化变更的能力,所有实体修改都不会被写入数据库,不需要手动操作EntityManager。
方案4:避免直接修改托管实体
如果只是需要用实体数据做临时计算、展示,直接将托管实体的属性拷贝到独立的POJO对象上操作,不要直接修改托管实体的属性,从根源上避免变更被自动持久化。
内容的提问来源于stack exchange,提问作者John Wong
相关产品推荐
相关产品推荐

