Spring Data JPA中Entity Graph的使用及findAll返回重复数据问题
嘿,我之前也踩过这个坑!用Entity Graph解决N+1查询问题的时候,很容易因为JPA底层的左连接逻辑返回重复的Department数据,我给你几个实用的解决办法:
先搞懂问题根源
你用@NamedEntityGraph加载Department的people集合时,JPA会自动用左外连接关联Department和Person表。如果一个部门有多个员工,查询结果里就会出现多条一模一样的Department记录(每条对应一个员工),而Spring Data JPA的默认findAll不会自动去重,所以就出现了重复数据。
方案1:数据库层面去重(最推荐)
直接在你的DepartmentRepository里给findAll方法加个带DISTINCT的自定义查询,同时保留EntityGraph注解:
public interface DepartmentRepository extends JpaRepository<Department, Long> { @Query("SELECT DISTINCT d FROM Department d") @EntityGraph(value = "department.p") List<Department> findAll(); }
这样数据库会直接返回唯一的Department记录,同时Entity Graph依然会加载对应的员工集合,既解决了N+1,又彻底搞定重复问题。
方案2:用SUBSELECT类型的Entity Graph
如果不想写自定义Query,也可以切换EntityGraph的查询模式为SUBSELECT。这种方式会分两次查询:先查所有Department的ID,再用一个子查询批量加载所有关联的Person,不会产生重复数据:
首先确保Department里的people关联配置正确:
@Entity @NamedEntityGraph(name = "department.p", attributeNodes = @NamedAttributeNode("people")) public class Department { @Id @GeneratedValue private long id; // 其他字段... @OneToMany(mappedBy = "department", fetch = FetchType.LAZY) private List<Person> people; // getter/setter... }
然后在Repository里指定EntityGraph的类型:
public interface DepartmentRepository extends JpaRepository<Department, Long> { @EntityGraph(value = "department.p", type = EntityGraph.EntityGraphType.SUBSELECT) List<Department> findAll(); }
这种方式也是1+1查询(不是N+1),性能同样优秀,而且不用写自定义SQL。
方案3:内存层面手动去重(不推荐)
如果上面两种方法都不适用,也可以在Service层对返回的列表手动去重,用LinkedHashSet还能保持原有的顺序:
// Service层代码 List<Department> rawDepartments = departmentRepository.findAll(); // 用LinkedHashSet去重并保留顺序 List<Department> distinctDepartments = new ArrayList<>(new LinkedHashSet<>(rawDepartments));
不过这个方法不推荐,因为会把重复数据先加载到内存再去重,浪费资源,优先用前两种数据库层面的解决方案。
内容的提问来源于stack exchange,提问作者qiabigin

