Spring Data JPA乐观锁生效场景咨询:CockroachDB Serializable隔离下
Spring Data JPA乐观锁适配与异常触发场景解析
一、Spring Data JPA适配乐观锁的步骤
Spring Data JPA对乐观锁的支持非常简洁,只需两步:
- 在实体类中添加版本字段,并用
@Version注解标记:
@Entity public class Account { @Id private Long id; @Version private Integer version; // 支持Integer、Long、Timestamp等类型 // 业务字段、getter/setter }
- 直接使用Spring Data JPA的Repository方法(如
save()、delete())操作实体即可。框架会自动在更新/删除SQL中加入版本校验条件(例如WHERE id = ? AND version = ?),当版本不匹配时,就会抛出OptimisticLockException。
二、你当前场景未触发异常的原因
你提到updateAccount总是查询到最新版本的实体,核心原因是CockroachDB的Serializable隔离级别会强制事务串行执行:当两个并发事务操作同一数据时,数据库会让其中一个事务等待另一个完成后再执行,第二个事务自然会读取到第一个事务提交后的最新版本,此时版本号匹配,乐观锁校验通过,不会抛出异常。
三、触发OptimisticLockException的典型场景
只有当应用层面拿到旧版本实体,且在该实体被其他事务修改并提交后,再尝试用旧版本更新时,才会触发异常,常见场景包括:
1. 长事务中的并发修改
事务A查询到实体(版本v1)后,因业务逻辑暂停(比如等待用户输入、调用第三方接口耗时),此时事务B启动:
- 事务B查询到同一实体(版本v1),修改后提交,版本号更新为v2;
- 事务A恢复执行,用手中的v1版本实体执行更新操作,此时SQL中的版本条件不匹配,Spring Data JPA会抛出
OptimisticLockException。
2. 同一事务内手动刷新实体后仍用旧实例修改
事务内先查询实体得到实例A(版本v1),此时其他事务修改并提交了该实体,版本变为v2;
- 调用
entityManager.refresh(实例A)获取最新版本,但如果你仍拿着之前的旧实例(未被刷新的对象)修改并保存,就会因版本不匹配触发异常。
3. 自定义SQL未携带版本校验
如果使用@Query编写自定义更新语句,但未手动加入版本条件:
@Modifying @Query("UPDATE Account SET balance = ?1 WHERE id = ?2") void updateBalance(BigDecimal balance, Long id);
此时若其他事务同时修改了该实体的版本号,后续再通过Spring Data JPA的常规方法操作该实体时,框架会检测到版本不匹配,抛出OptimisticLockException。
4. 绕过一级缓存获取旧版本实体
默认情况下,Spring Data JPA会缓存同一事务内查询到的实体,若你通过其他方式(比如直接调用JDBC、另一个EntityManager)获取了旧版本实体,修改后保存也会触发异常。
内容的提问来源于stack exchange,提问作者Minh Trần
相关产品推荐
相关产品推荐

