首页 > 解决方案 > JPQL,是否可以使我选择的对象的子集合不同?

问题描述

我有以下 JPQL 查询:

@Query("SELECT project FROM Project project JOIN FETCH project.employees employee JOIN FETCH project.order ord " +
          "JOIN FETCH ord.customer JOIN FETCH project.defaultDailyEntrySettings LEFT JOIN FETCH employee.projectEmployeeRoles role " +
          "LEFT JOIN FETCH role.project roleProject LEFT JOIN FETCH roleProject.defaultDailyEntrySettings " +
          "WHERE project.id = :id")
Project test(@Param("id") long id);

问题是它返回内部重复的员工project.employeesemployee.projectEmployeeRoles对于员工内部的每个角色,它在project.employees列表中出现一次。那是由JOIN FETCH employee.projectEmployeeRoles role. 所以如果员工有 7 个角色,它将在project.employees列表中出现 7 次。有没有办法project.employees区分,或者有没有其他方法可以确保员工只在列表中出现一次?我可以在 java 中删除重复项,但如果查询首先不返回任何重复项会更好。

如果我删除LEFT JOIN FETCH结果是正确的并且不包含任何重复的员工。但是这里的问题是employee.projectEmployeeRoles每个员工都懒惰地获取,这会导致太多的性能问题。

编辑:

我将 JPA 与休眠一起使用。以下是上述实体的连接定义:

项目:

@Data
public class Project {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  @ManyToMany
  @JsonIgnore
  @JoinTable(
          name = "employee_projects",
          joinColumns = @JoinColumn(name = "project_id"),
          inverseJoinColumns = @JoinColumn(name = "employee_id")
  )
  private List<Employee> employees;

  @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
  private List<ProjectEmployeeRole> projectEmployeeRoles;

  // more fields
}

员工:

@Data
public class Employee {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  @ManyToMany
  @JsonIgnore
  @JoinTable(
          name = "employee_projects",
          joinColumns = @JoinColumn(name = "employee_id"),
          inverseJoinColumns = @JoinColumn(name = "project_id")
  )
  private List<Project> projects;

  @ManyToMany
  @JsonIgnore
  @JoinTable(
          name = "employee_roles",
          joinColumns = @JoinColumn(name = "employee_id"),
          inverseJoinColumns = @JoinColumn(name = "role_id")
  )
  private Set<ProjectEmployeeRole> projectEmployeeRoles;

  // more fields
}

角色:

public class ProjectEmployeeRole {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  @ManyToOne
  @JsonIgnore
  @NotNull
  private Project project;

  @ManyToMany
  @JsonIgnore
  @JoinTable(
          name = "employee_roles",
          joinColumns = @JoinColumn(name = "role_id"),
          inverseJoinColumns = @JoinColumn(name = "employee_id")
  private List<Employee> employees;

  // more fields

编辑2:

我现在一次加载一个集合,并且能够避免 n+1 请求和笛卡尔积。我也没有任何重复了:

服务:

Project project = projectRepository.findProjectInOneRequestById(id);
project.setEmployees(employeeRepository.findIneOneRequestByEmployees(project.getEmployees()));

查询:

 @Query("SELECT project FROM Project project LEFT JOIN FETCH project.employees employee JOIN FETCH project.order ord " +
          "JOIN FETCH ord.customer LEFT JOIN FETCH project.defaultDailyEntrySettings " +
          "WHERE project.id = :id")
  Project findProjectInOneRequestById(@Param("id") long id);

@Query("SELECT DISTINCT employee FROM Employee employee LEFT JOIN FETCH employee.projectEmployeeRoles role " +
          "LEFT JOIN FETCH role.project roleProject WHERE employee IN :employees")
  List<Employee> findIneOneRequestByEmployees(@Param("employees") List<Employee> employees);

标签: javajpql

解决方案


是的,当您不使用 JOIN FETCH 时,这是可能的。在您的情况下,Hibernate 似乎正在生成一个笛卡尔积作为您的查询结果。我注意到您正在使用 Set in private Set<ProjectEmployeeRole> projectEmployeeRoles;。如果将其更改为 List,则可能会收到 MultipleBagFetchException。我猜你之前遇到过这个问题,并且正在尝试使用 Set 来解决它,但结果是现在你有了一个笛卡尔积。

我建议您在此处评估是否真的需要 JOIN FETCH。为了理解 Hibernate 的笛卡尔问题以及如何处理 MultipleBagFetchException 我建议这些文章:

MultipleBagFetchException

笛卡尔积 - 休眠


推荐阅读