3.9 KiB
3.9 KiB
LinkedIn Draft & Consultant Review: The JPA @Version Field That Turns a Silent Lost Update Into an HTTP 409
文档性质:从
blog.hoelee.com改写的 LinkedIn 待发布技术动态。 发布状态:待顾问审阅 (未在 LinkedIn 发布)源文章:https://blog.hoelee.com/posts/jpa-version-field-lost-update/ 系统标识:jpa-version-field-lost-update| 分类:engineering字数:2061 chars (LinkedIn 上限 3,000 chars)
一、 核心背景与铁律约束提示(供顾问审阅对照)
- 求职画像契合(Senior 全栈/DevOps/AI 架构师):严禁流露“做生意的老板/小公司创始人”倾向;一切商业实战均转换为“生产系统架构、高可用交付与自动化运维”视角。
- 纯洁人设与零禁用词:绝对禁止
homelab,home lab,家用,小主机,玩具,折腾,玩票等词汇。必须使用the infrastructure I manage,my self-hosted production nodes,production host。 - 第一人称单数:统一使用单数
I/我,杜绝虚张声势的复数we。 - 数据绝对保真:所有数字、性能比率、吞吐提升、故障现象严禁凭空捏造。
- OPSEC 安全脱敏:无内部绝对路径(如
D:/dev),无真实私有内网 IP,公网 IP 必须转为文档保留地址。
二、 LinkedIn 拟发布动态文案
Two users open the same record in a web application. Both make changes. Both click save.
User A saves first; the database updates and the API returns HTTP 200. User B saves two seconds later; the database overwrites User A's changes, and the API returns HTTP 200 again.
User A's work has completely vanished. No exception was logged, no alert fired, and both clients were explicitly told their save was successful.
This is the classic **lost update problem**, and it is one of the quietest data loss defects in read-modify-write REST APIs.
Here is how I implemented end-to-end optimistic locking to eliminate this failure mode in a Spring Boot (Java 21) service:
1. Add `@Version` to the Entity
Annotate an integer or timestamp column with `@Version`. On every update, Hibernate automatically appends a version check to the SQL query:
`UPDATE posts SET title=?, body=?, version=version+1 WHERE id=? AND version=?`
2. Make the Version Part of the API Contract
Never hide the version field inside server-side session state. Expose `version` in the read DTO and mandate it in the write request payload (`UpdatePostRequest`). If the client does not submit the version it read, the request is rejected immediately.
3. Exception Handling & REST Cleanliness
When two concurrent requests race, Hibernate detects 0 rows updated and throws an `OptimisticLockException` (wrapped by Spring as `ObjectOptimisticLockingFailureException`).
A centralized `@RestControllerAdvice` catches this concurrency failure and translates it into a clean `HTTP 409 Conflict` with a structured payload:
`{ "error": "CONFLICT", "message": "Record was modified by another transaction. Please reload and reapply your changes." }`
An error is infinitely better than a deceptive HTTP 200. A 409 allows the client application to prompt the user to merge changes, preventing silent data loss before it corrupts production state.
How does your backend architecture handle concurrent writes across distributed clients?
Write-up: https://blog.hoelee.com/posts/jpa-version-field-lost-update/
三、 外部顾问审阅评估反馈单
- 审阅结论:[ ] 通过 / [ ] 需微调 / [ ] 需重构 / [ ] 建议暂缓
- 1. 职场形象评估(是否符合高级技术雇佣人设,无老板/卖货感):
- 2. 技术真实性与逻辑推导(因果链条与数据是否严谨):
- 3. 语言地道度与人设去油(是否纯正地道、零业余玩具感):
- 4. 优化修改建议(如有优化建议或修改文案,请在此批注):