Hibernate二次更新对象报StaleObjectStateException异常排查
问题表现
实现产品信息自由更新功能时,携带字段修改内容的更新操作第二次提交就会触发报错,错误包含提示Cannot update an object more than once,完整异常信息:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [guru.springframework.domain.Product#4]
复现规律:
- 提交的JSON请求体无字段修改时,多次重复提交均可正常执行
- 请求体携带字段修改内容时,仅第一次提交能成功,第二次及后续提交直接抛出上述异常
现有业务代码
Controller层代码
@ApiOperation(value = "Update a product") @RequestMapping(value = "/update/", method = RequestMethod.PUT, produces = "application/json") public ResponseEntity<EntityModel<Product>> updateProduct(@RequestBody Product product){ return productService.updateProduct(product); }
Service实现层代码
@Override public ResponseEntity<EntityModel<Product>> updateProduct(Product product) { boolean exist=productRepository.existsById(product.getProductId()); if(exist){ productRepository.save(product); return ResponseEntity.ok().body(productEntityFactory.toModel(product)); }else { throw new EntityNotFoundException("There is no product with the given ID, so it can't be updated"); } }
问题根因
核心原因是乐观锁版本号不同步:
Product实体类配置了@Version注解实现乐观锁并发控制,现有更新逻辑直接使用前端传入的脱管态Product实体执行save操作:
- 第一次携带修改内容提交时,前端传入的version值和数据库存储的当前version值一致,Hibernate正常执行更新语句,同时自动将该条记录的version值+1,操作成功
- 第一次更新成功后,接口返回给前端的还是旧版本的实体数据,前端没有拿到更新后的最新version值
- 第二次携带修改内容提交时,前端传入的version值比数据库中存储的当前version值小,Hibernate判定为乐观锁冲突,直接抛出
StaleObjectStateException - 无修改的重复提交能正常执行,是因为Hibernate检测到实体所有字段值和数据库完全一致,不会实际执行update语句,不会触发版本校验逻辑。
修复方案
两种方案按需选择即可:
- 方案1(推荐):更新时先查询数据库中的持久态实体,拷贝前端传入的更新字段后再执行save,返回保存后的最新实体(携带更新后的version值)给前端,前端后续更新时携带最新version值即可。修改后的Service层代码:
@Override public ResponseEntity<EntityModel<Product>> updateProduct(Product product) { return productRepository.findById(product.getProductId()) .map(persistProduct -> { // 拷贝字段时跳过id、version字段,避免被前端传入的旧值覆盖 BeanUtils.copyProperties(product, persistProduct, "productId", "version"); Product updatedProduct = productRepository.save(persistProduct); return ResponseEntity.ok().body(productEntityFactory.toModel(updatedProduct)); }) .orElseThrow(() -> new EntityNotFoundException("There is no product with the given ID, so it can't be updated")); }
优先使用查询出的持久态实体做更新,不要直接保存前端传入的脱管态实体,可以规避绝大多数JPA/Hibernate更新场景的异常。
- 方案2:如果业务场景不存在多端同时修改同一条产品数据的并发场景,可以直接移除Product实体类上的
@Version注解关闭乐观锁,该方案会丢失并发更新冲突防护能力,非必要不推荐。
内容的提问来源于stack exchange,提问作者postFix
相关产品推荐
相关产品推荐

