Hibernate重复删除实体的异常问题及原理验证咨询
Hibernate EntityManager.remove() 删除行为解析
我在使用Hibernate删除实体时遇到了两种不同的执行结果,数据库中仅有一条目标记录,通过EntityManager的remove()方法执行删除操作。
第一段代码与现象
@Transactional public void deleteEmployee(Long id) { for (int i = 0; i < 3; i++) { Employee result = (Employee) employeeDao.findEmployeeById(id); employeeDao.deleteEmployee(result); employeeDao.deleteEmployee(result); } }
执行现象:第一次循环内连续两次调用deleteEmployee(result),全程未抛出任何异常。
第二段代码与现象
@Transactional public void deleteEmployee(Long id) { for (int i = 0; i < 3; i++) { Employee result = (Employee) employeeDao.findEmployeeById(id); employeeDao.deleteEmployee(result); } }
执行现象:第二次循环执行时抛出异常:
org.springframework.dao.InvalidDataAccessApiUsageException: attempt to create delete event with null entity
原理详解与理解验证
你的核心理解方向是正确的,下面补充Hibernate持久化上下文的具体运作细节,帮你彻底理清:
1. 持久化上下文的状态流转
- 当通过
findEmployeeById(id)获取实体时,Hibernate会将实体存入一级缓存(持久化上下文),标记为Managed(托管)状态,此时实体与数据库记录一一对应。 - 调用
EntityManager.remove(entity)时,Hibernate不会立刻执行数据库删除SQL,而是将实体的状态改为Removed(删除标记),并将其从Managed集合移出(但暂时保留在缓存中)。这个变更仅存在于内存,直到事务提交时才会执行实际的数据库删除操作。
2. 同一循环内两次删除无异常的原因
- 第一次调用
remove()后,实体状态变为Removed,但实体对象本身并没有被销毁,仍然是一个非null的Java对象。 - 第二次调用
remove()时,Hibernate会先检查实体的当前状态:如果已经是Removed,则直接跳过后续操作,不会抛出任何异常——因为传入的实体对象本身不是null,只是状态已标记为删除。
3. 第二次循环抛出异常的原因
- 第一次循环结束后,实体已被标记为
Removed。当第二次循环调用findEmployeeById(id)时:- Hibernate优先查询一级缓存,发现该实体状态为
Removed,会直接返回null(Hibernate认为该实体已被标记为删除,不再是有效的托管实体); - 此时
deleteEmployee(null)被调用,Spring的DAO层检测到传入的实体为null,就会抛出InvalidDataAccessApiUsageException异常。
- Hibernate优先查询一级缓存,发现该实体状态为
补充说明
- 事务提交时机:整个方法被
@Transactional注解标记,事务会在方法执行完毕后才提交。也就是说,直到三次循环全部结束,Hibernate才会执行一次数据库删除SQL(即使多次调用remove(),同一个实体只会生成一条删除SQL)。
内容的提问来源于stack exchange,提问作者Manish Sharma
相关产品推荐
相关产品推荐

