第 11 篇 · 共100篇|用代码丈量成长 ------ 坚持写下去,就是最好的成长。
Hibernate:你不 save,我也要帮你 UPDATE
那天在排查一个挺诡异的事:
某个分表系统(用的是 ShardingSphere),日志里出现了奇怪的更新。明明业务逻辑只改了一条数据,最后路由日志却显示更新了好几张分表。
最开始我们都以为是 SQL 写错了,或者 ShardingSphere 的分片配置有问题。结果一通跟踪下来,发现问题根本不在 SQL,而在 JPA 自己干的"好事" 。
"我没 save(),它自己就 UPDATE 了?"
当时业务代码大概长这样:
scss
@Transactional
public void updateOrder() {
Order order = orderRepo.findById(9527L).orElseThrow();
order.setStatus("PAID");
order.setUpdateTime(new Date());
}
没调用 save(),也没写 update 语句,但提交事务时,数据库那边真的多了一条 UPDATE。
一开始我还以为是同事哪里调用错了,后来把日志和 SQL 都跟了一遍,才想起来------这是 JPA 的"脏检查"(Dirty Checking)。
JPA 查出来的实体在事务里是托管状态,Hibernate 会在事务提交前比对对象的快照,如果字段被改了,就自动发 UPDATE。
这机制平时挺贴心的,但在分表系统里就变成了个坑。
分表的锅:一个小 UPDATE,变成多表广播
JPA 发的 UPDATE 语句是:
bash
update orders set status=?, update_time=? where id=?
如果分片键不是 id,或者 WHERE 条件没带上分片字段,ShardingSphere 就懵了------它不知道该发到哪张分表去,只能"全表广播更新"。
日志里就能看到:
sql
Logic SQL: update orders set status=?, update_time=? where id=?
Actual SQL: update orders_1001_2025 ...
Actual SQL: update orders_1002_2025 ...
一条逻辑 SQL,打到了两张甚至多张分表上。
看着这些 UPDATE 日志,心里直犯嘀咕:这谁背得起啊。
对照实验:同样的逻辑,不同的状态
为了确认是不是 JPA 自动干的,我们做了几个小实验。
情况一:有事务,自动 UPDATE
typescript
@Transactional
public void caseA() {
Order o = repo.findById(9527L).orElseThrow();
o.setStatus("PAID");
o.setUpdateTime(new Date());
}
提交事务时 Hibernate 会自动发 UPDATE。
情况二:detach 后再改,不会 UPDATE
scss
@Transactional
public void caseB() {
Order o = repo.findById(9527L).orElseThrow();
entityManager.detach(o);
o.setStatus("CANCELLED");
}
因为被 detach 掉了,不再是托管对象,Hibernate 就不会再跟踪。
只是这种写法容易破坏工作单元的一致性,后续再 merge 回去挺麻烦,不太推荐。
情况三:只读事务,最干净的做法
ini
@Transactional(readOnly = true)
public UserDTO view(Long id) {
User u = userRepo.findById(id).orElseThrow();
return toDto(u);
}
只读事务 Hibernate 默认不 flush,用在纯查询场景下特别合适。
这种写法读起来就安全,不用担心哪天有人无意中改了个字段把数据库带跑偏。
最后的方案其实挺简单:
查询完直接转 DTO,再改 DTO。
这样实体就不会处在托管状态,也不会触发自动更新。
从那之后,凡是看到有事务的地方改实体对象,大家都会多问一句:"这个改动是要落库的,还是只是展示?"
有时候小小一个约定,能省下很多奇怪的线上故障。
一点后话
其实 JPA 的脏检查机制挺聪明的,它帮我们省掉了很多 save() 的麻烦。只是当系统复杂起来,比如有分表、有中间件、有异步逻辑时, "自动"反而成了风险。
那次排查让我意识到,ORM 的便利是有边界的。它能帮你管理状态,但你得清楚自己在哪个状态里动了手。
有时候写代码不是在防 bug,而是在防"好心办坏事"。
你的阅读与同行,让路途更有意义
愿我们一路向前,成为更好的自己