
Spring Data JPA 的 save() 默认执行“插入或合并”逻辑:若实体 ID 不存在则插入,存在则更新;如需严格更新(ID 不存在时抛异常),应先调用 findById() 校验再操作。
spring data jpa 的 `save()` 默认执行“插入或合并”逻辑:若实体 id 不存在则插入,存在则更新;如需严格更新(id 不存在时抛异常),应先调用 `findbyid()` 校验再操作。
在 Spring Data JPA(配合 Hibernate 6)中,JpaRepository.save() 方法的行为由底层 JPA 规范和 Spring Data 实现共同决定,并非简单的“更新”操作。其核心逻辑如下:
@Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity); // ID 为空或判定为新实体 → 执行 INSERT
return entity;
} else {
return this.em.merge(entity); // ID 存在 → 执行 SELECT + UPDATE(或 INSERT 若未查到)
}
}关键点在于 entityInformation.isNew(entity) 的判断逻辑。对于使用 @GeneratedValue(strategy = GenerationType.IDENTITY)(如本例中的 IdentityGenerator)的主键,Spring Data JPA 默认将所有带非空 ID 的实体视为“可能已存在”,从而进入 merge() 分支。而 merge() 是 JPA 标准方法:它会先根据 ID 查询数据库,若未查到,则自动转为 INSERT —— 这正是你观察到“ID=10 不存在却插入新记录”的根本原因。
⚠️ 注意:Javadoc 中提到的 “Also thrown if the entity is assumed to be present but does not exist in the database” 指的是 乐观锁失败(OptimisticLockingFailureException)场景,并非针对 ID 不存在的常规更新。该异常仅在实体带有 @Version 字段、且数据库中存在同 ID 记录但版本不匹配时触发,不适用于 ID 完全不存在的情况。
✅ 推荐解决方案:显式校验 + 安全更新
最清晰、符合契约、且可维护的方式是主动检查存在性,而非试图覆盖 save() 行为:
@Service
public class TestService {
@Autowired
private TestRepository testRepository;
public Test updateStrictly(Integer id, String newName) {
// 步骤1:强制校验记录存在
Test existing = testRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(
"Test entity with id " + id + " does not exist"));
// 步骤2:更新业务字段(保持 ID 不变)
existing.setName(newName);
// 步骤3:保存(此时 isNew() 返回 false,走 merge → UPDATE)
return testRepository.save(existing);
}
}❌ 不推荐的做法(及原因)
重写
SimpleJpaRepository并调用已弃用的session.update():Session.update()在 Hibernate 6 中已被标记为@Deprecated,因其绕过一级缓存、忽略脏检查、易引发NonUniqueObjectException,且与 JPA 生命周期严重脱节,强烈不建议在新项目中使用。自定义
isNew()判定逻辑(如覆写JpaEntityInformation):
虽技术可行,但会破坏 Spring Data JPA 的通用行为一致性,增加测试与维护成本,且可能影响其他仓库操作(如deleteById)。依赖数据库唯一约束并捕获 SQL 异常:
不够语义化(插入失败 ≠ 业务逻辑错误),异常类型与层级不明确(如DataIntegrityViolationException需额外解析),违背“快速失败、明确意图”的设计原则。
? 总结
save() 的“存在即更新、不存在即插入”是 JPA 和 Spring Data 的有意设计,旨在支持 RESTful 资源创建/替换(如 PUT 语义)。若业务要求严格更新(update-only),请始终遵循以下模式:
-
先查(
findById) —— 明确表达“我预期该记录必须存在”; -
后改后存(
save) —— 利用现有机制安全完成更新; -
善用事务与异常传播 —— 确保原子性,并向调用方传递准确的业务语义(如
EntityNotFoundException)。
这不仅符合框架约定,也提升了代码可读性、可测试性与长期可维护性。

















