Spring Data JPA为何用findById做删除校验?是否需提前校验?
关于Spring Data JPA删除操作的两个问题解答
问题1:SimpleJpaRepository为何用findById()校验存在后再执行删除?
SimpleJpaRepository的deleteById底层采用findById校验+删除的逻辑,核心原因有两点:
- JPA规范要求:JPA的
EntityManager.remove()方法仅能操作处于持久化状态的实体。通过findById加载实体,能确保该实体被纳入持久化上下文,后续删除操作完全符合JPA规范,避免部分JPA提供商因直接操作ID而出现兼容性问题。 - 异常统一处理:通过
findById().orElseThrow()可以稳定抛出EmptyResultDataAccessException,让删除操作的异常类型与其他查询类操作保持一致,方便上层代码统一捕获和处理。
当然这种实现确实存在小缺陷:如果是大实体,加载整个对象会浪费资源。这种场景下可以自定义JPQL删除语句(比如@Modifying @Query("delete from User u where u.id = ?1")),跳过实体加载直接执行删除。
问题2:调用deleteById前用existsById校验是否冗余?
完全冗余,甚至会引发额外问题:
- 多一次无效查询:
existsById会执行一条select count(1)的存在性查询,而deleteById内部又会执行findById校验,等于平白多了一次数据库请求,浪费资源。 - 并发安全隐患:两次查询之间存在时间窗口,若在
existsById校验通过后、deleteById执行前,其他线程删除了该实体,deleteById仍会抛出异常,自定义校验根本没起到预期作用。
正确的做法是直接调用deleteById,捕获它抛出的异常并转换成自定义异常:
public void delete(String id) { try { userRepository.deleteById(id); } catch (EmptyResultDataAccessException e) { throw new ObjectNotFoundException(); } }
这样既避免了冗余查询,又能正确处理实体不存在的情况,同时消除并发场景下的潜在问题。
补充:《The best way to write a Spring Data Exists Query》提到不要用
findBy查询模拟存在性校验,是因为这类查询会加载整个实体并传输数据,资源消耗大;而existsById是专门的存在性查询,仅返回布尔值,资源消耗更低,但即便如此,在deleteById前用它校验依然多余——毕竟deleteById内部已经完成了存在性检查。
内容的提问来源于stack exchange,提问作者Martin Kršek
相关产品推荐
相关产品推荐

