JPA Repository查询返回重复结果:findById获取关联集合重复原因求解
问题根因分析
直接原因:EAGER加载+左外连接产生笛卡尔积
你在@OneToMany注解上配置了fetch=FetchType.EAGER,Hibernate处理findById这类单主键查询时,默认会用左外连接的方式一次性加载关联的Vacation数据,生成的SQL逻辑大致如下:
SELECT t.*, v.* FROM teacher t LEFT JOIN vacation v ON t.id = v.teacher_id WHERE t.id = 1
如果你的Teacher实体还有其他EAGER类型的多值关联,或者Vacation实体本身也配置了EAGER加载的多值关联,左外连接就会产生笛卡尔积:比如你有2条假期数据,笛卡尔积就会返回4行结果,Hibernate默认不会对List类型的关联集合做去重,最终就会出现每条假期重复一次的4条结果。
先执行findAll结果正常的原因
- 执行
findAll()查询教师列表时,Hibernate默认不会用左外连接加载EAGER关联,而是采用N+1的查询策略:先查询所有Teacher的基础字段,再按每个教师的ID单独执行SQL查询关联的假期数据,这种单条查询假期的方式不会产生笛卡尔积,得到的2条假期数据是正常的。 - 此时查询到的完整
Teacher实体会被存入Hibernate的一级缓存(持久化上下文),后续你再执行findById(1)时,Hibernate会直接从缓存中取出已经加载完成的Teacher对象,不会再执行左外连接的查询SQL,自然不会出现重复数据。
可选解决方案
- 方案一:将
List<Vacation>改为Set<Vacation>接收假期数据,利用Set的去重特性自动过滤重复元素。 - 方案二:自定义查询语句,手动加
distinct关键字去重,示例:// 在TeacherRepository中添加自定义方法 @Query("SELECT DISTINCT t FROM Teacher t LEFT JOIN FETCH t.vacations WHERE t.id = :id") Optional<Teacher> findTeacherWithVacationsById(@Param("id") Integer id); - 方案三:若非必要,将
FetchType.EAGER改为默认的FetchType.LAZY,在需要用到假期的业务场景手动触发关联加载,从根源避免EAGER加载带来的各类副作用。
内容的提问来源于stack exchange,提问作者Ilyas Saitov
相关产品推荐
相关产品推荐

