Spring Boot 2.x升3.x后findById触发ListResultsConsumer重复行断言错误
问题原因分析
- Hibernate 6的唯一语义检查差异:
findById在Hibernate 6中默认启用UniqueSemantic.ASSERT语义,会在内存层面严格校验查询结果的唯一性,一旦检测到重复的实体/代理实例就抛出异常;而findAllById属于集合查询,默认不启用该严格校验,即便内存中有重复实例也不会触发报错。 - 双向即时加载引发的内存重复:你的实体存在双向EAGER加载的循环关联(如
Question与QuestionI18n互相即时加载),执行findById时,Hibernate加载主实体的同时会递归加载关联的QuestionI18n,而QuestionI18n的复合主键又会即时加载回Question,这个过程中Hibernate可能生成重复的实体实例,触发了ASSERT的内存检查。
解决方案
1. 调整关联加载策略(推荐)
打破双向EAGER的循环引用,将其中一方的关联改为LAZY加载。例如修改QuestionI18n的关联配置:
@ManyToOne(fetch = FetchType.LAZY) @MapsId("questionId") // 若为复合主键关联,配合MapsId使用更规范 private Question question;
这种方式从根源上避免了递归加载导致的内存重复实例问题。
2. 关闭findById的唯一语义检查
在Repository的findById方法上通过查询提示禁用严格唯一校验:
import org.hibernate.annotations.QueryHints; import jakarta.persistence.QueryHint; public interface QuestionRepo extends JpaRepository<Question, Long> { @Query("select q from Question q where q.id = ?1") @QueryHints(@QueryHint(name = QueryHints.UNIQUE_SEMANTIC, value = "ALLOW")) Optional<Question> findById(Long id); }
ALLOW语义会跳过内存中的重复检查,结合数据库的唯一约束,不会出现实际的数据重复问题。
3. 规范复合主键映射
如果QuestionI18n使用复合主键,确保主键关联的映射逻辑正确,避免不必要的EAGER加载。示例如下:
// 复合主键类 @Embeddable public class QuestionI18nId implements Serializable { private Long questionId; private String locale; // getters、setters、equals、hashCode实现 } // 关联实体类 @Entity public class QuestionI18n { @EmbeddedId private QuestionI18nId id; @ManyToOne(fetch = FetchType.LAZY) @MapsId("questionId") // 将主键字段与关联实体绑定 private Question question; // 其他业务字段 }
这种配置方式能避免主键实体中的循环即时加载问题。
内容的提问来源于stack exchange,提问作者Stuck
相关产品推荐
相关产品推荐

