@Version 注解是 JPA 实现乐观锁的核心机制,通过在 UPDATE 语句的 WHERE 条件中校验版本字段(如 Integer、Timestamp 等)确保并发安全;使用时须先查询再更新、捕获 OptimisticLockException、避免非事务更新及序列化丢失,并注意 save() 误用、批量操作绕过校验等问题。

@Version 注解是 JPA 规范中用于实现乐观锁的核心机制,主流 ORM 框架(如 Hibernate、EclipseLink)通过它自动在 SQL 层面加入版本校验逻辑,避免脏写覆盖。
版本字段如何被识别和使用
标注 @Version 的字段必须满足:类型为 int/Integer、long/Long、short/Short、java.time.Timestamp 或实现了 java.io.Serializable 的自定义类型(较少用)。JPA 提供方会在实体加载时记录该值,在执行 UPDATE 时将其作为 WHERE 条件的一部分参与比对。
- 例如实体有
@Version private Integer version;,执行更新时生成的 SQL 类似:UPDATE user SET name = ?, version = ? WHERE id = ? AND version = ? - 若数据库中当前 version 值与内存中旧值不一致(说明已被其他事务修改),WHERE 条件不成立,影响行数为 0,框架抛出 OptimisticLockException
实际开发中需要配合的操作
仅加注解还不够,需注意以下关键点:
- 每次更新前必须先查询:乐观锁依赖“读取 → 修改 → 写入”流程中的原始版本号,不能直接 new 实体后 set ID 和 version 去 merge/update
- 捕获并处理 OptimisticLockException:业务层应捕获该异常,决定重试、提示用户或合并变更。Hibernate 默认不重试,需手动实现
- 避免在非事务方法中更新带 @Version 的实体:无事务时 flush 可能失败,且版本检查逻辑可能被跳过
- 慎用 @Version 字段做业务逻辑判断:它只保证并发安全,不代表业务上的“第几次修改”,不应依赖其数值含义
常见误区与注意事项
有些场景下 @Version 表现不符合预期,往往源于配置或使用方式问题:
立即学习“Java免费学习笔记(深入)”;
- 使用 save() 而非 merge() 或 find() + setXXX() + save():Spring Data JPA 的 save() 对新实体是 INSERT,对已有 ID 实体默认走 UPDATE,但若未先查出旧数据,version 字段可能为 null 或 0,导致 WHERE 条件恒不成立
- 实体被序列化/反序列化后 version 丢失:比如 JSON 转换时未包含 version 字段,再传回更新就会出错
- 批量更新绕过 ORM:原生 SQL、@Modifying + @Query 或 JPQL 批量操作不会触发 @Version 校验,需自行加 version 条件或改用逐条处理
扩展:自定义版本策略(可选)
除数字递增外,也可用 Timestamp 类型实现时间戳版本控制:
@Version @Column(name = "last_modified_time") private Timestamp lastModifiedTime;- Hibernate 会自动在每次更新时设置为当前时间,并在 WHERE 中校验该时间戳是否仍为上次读取的值
- 适合对“最后修改时间”本身有业务需求的场景,但要注意数据库时钟一致性问题


















