Hibernate实体UUID主键场景下StaleStateException异常问题
问题原因
这个异常是两个问题叠加导致的:
- 首先你的UUID主键没有配置正确的ID生成规则:你在id字段上写的无参
@GeneratedValue()默认使用GenerationType.AUTO策略,在Hibernate 5.6版本中,该策略不会为UUID类型自动绑定内置的UUID生成器,而是会按照应用自定义ID的逻辑处理字段。 - 其次Spring Data JPA默认的
deleteAll()方法并不会直接执行全表删除SQL,它的实现逻辑是先调用findAll()把表中所有记录加载到持久化上下文(一级缓存)标记为托管/待删除状态,等到flush阶段再逐个生成delete from xxx where id=?的单条删除语句执行。
当你在同个事务里紧接deleteAll()调用saveAll()时,因为UUID字段没有专属的ID生成器,Hibernate给id为null的新实体分配ID时,会错误复用一级缓存中已经标记为删除的旧实体ID值,导致持久化上下文的动作队列状态错乱:部分删除语句执行时,对应ID的记录已经被提前处理/不存在,最终出现「预期影响1行,实际影响0行」的StaleStateException。
换成Long类型主键后问题消失,是因为Long类型适配默认AUTO策略时,Hibernate会正确绑定数据库自增/序列生成器,给新实体生成完全独立、不和已删除实体重复的ID值,删除和插入动作对应的ID没有重叠,自然不会出现状态冲突。
修复方案
你可以任选以下一种方案修复,优先推荐前两种:
- 方案1:给UUID主键配置正确的生成策略(根因修复)
把实体类id字段的注解修改为如下形式,显式指定Hibernate内置的UUID生成器,从根源避免ID复用问题:@Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator") @Column(columnDefinition = "BINARY(16)") // 如果你用字符串存UUID可以改成CHAR(36),BINARY(16)存储性能更好 private UUID id; - 方案2:替换全表删除的实现,绕过一级缓存
既然你的逻辑是清空全表再导入新数据,完全不需要用逐个加载再删除的低效deleteAll(),直接在Repository中定义自定义的全表清空方法,直接执行TRUNCATE语句,不会把旧实体加载到缓存中:
业务逻辑里把原来的// 在Technology对应的Repository接口中添加方法 @Modifying @Transactional @Query(value = "TRUNCATE TABLE technology", nativeQuery = true) void truncate();deleteAll()换成调用truncate()即可,这种方式不仅能避免缓存冲突,批量数据的删除性能也远高于默认的deleteAll()。 - 方案3:手动刷新清空持久化上下文
如果你不想修改实体注解和Repository方法,可以在deleteAll()执行后立刻强制flush删除动作、清空一级缓存,再执行保存逻辑:
这里的techRadarRepository.deleteAll(); entityManager.flush(); // 立刻把所有删除语句刷到数据库执行 entityManager.clear(); // 清空一级缓存,清除所有托管的旧实体 techRadarRepository.saveAll(technologies);entityManager可以通过Spring依赖注入直接获取。
内容的提问来源于stack exchange,提问作者Obaid Maroof
相关产品推荐
相关产品推荐

