JPQL,获得具有子列表的对象列表,避免n + 1个请求,仅选择特定字段

问题描述 投票:1回答:1

我具有以下实体Project

@Data
@NoArgsConstructor
@Entity
@ToString(exclude = "roles")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Project {

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

  @Column(unique = true)
  private String name;

  private String description;

  private Boolean isArchived;

  private LocalDate archivedDate;

  private LocalDate creationDate;

  @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT FALSE")
  private Boolean invoicingActivated;

  @ManyToOne
  @NotNull
  private Order order;

  @OneToOne(cascade = CascadeType.ALL)
  private DefaultDailyEntrySettings defaultDailyEntrySettings;

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

我想获得所有项目。每个项目还应具有其projectEmployees的列表。

即实体ProjectEmployee

@Data
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"employee_id", "project_id"})})
@NoArgsConstructor
@ToString(exclude = "project")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProjectEmployee {

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

  @ManyToOne
  @JsonIgnore
  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
  @NotNull
  private Project project;

  @ManyToOne
  @NotNull
  private Employee employee;

  @ManyToOne
  private ProjectEmployeeRole projectEmployeeRole;
}

为了避免n + 1个查询,我编写了以下查询:

@Query("SELECT project FROM Project project JOIN FETCH project.order ord JOIN FETCH ord.customer " +
          "LEFT JOIN FETCH project.projectEmployees projectEmployee LEFT JOIN FETCH project.defaultDailyEntrySettings " +
          "LEFT JOIN FETCH projectEmployee.employee LEFT JOIN FETCH projectEmployee.projectEmployeeRole")
List<Project> findAllProjectsInOneQuery();

这有效,但是它返回每个对象的all属性。例如,我只对ord.customer的ID和名称感兴趣,在这种情况下,我不需要ord.customer的所有其他字段。以这种方式获取所有字段的问题是,有很多数据正在传输,在这种情况下我不需要。只选择我需要的内容并减少通过互联网发送的数据量,我可以这样做:

@Query("SELECT new de.project.Project(project.id, project.name, " +
          "project.description, project.isArchived, project.archivedDate, " +
          "project.creationDate, project.invoicingActivated, project.order.id, " +
          "project.order.name, project.order.customer.id, project.order.customer.name) " +
          "FROM  Project project")
List<Project> findAllMinimal();

但是,正如您所看到的,我无法以这种方式获取project.projectEmployees,因为它是一个列表,而且我认为我无法以这种方式通过构造函数传递列表。

我尝试过:

@Query("SELECT new de.project.Project(project.id, project.name, " +
              "project.description, project.isArchived, project.archivedDate, " +
              "project.creationDate, project.invoicingActivated, project.order.id, " +
              "project.order.name, project.order.customer.id, project.order.customer.name, " +
              "projectEmployee.id) " +
              "FROM  Project project JOIN project.projectEmployees projectEmployee")
    List<Project> findAllMinimal();

但是projectEmployee.id只是第​​一个projectEmployee的ID,我认为我无法以这种方式传递所有projectEmployees

是否有任何方法可以使用其projectEmployees(以及上面查询中列出的其他属性)来获取所有项目,并指定我想要获取哪些字段?它不必是一个查询,恒定数量的查询就可以了。显然应该避免n + 1个查询。

编辑:

我想出了一种解决方法。我正在使用以下两个查询:

@Query("SELECT new de.project.Project(project.id, project.name, " +
          "project.description, project.isArchived, project.archivedDate, " +
          "project.creationDate, project.invoicingActivated, project.order.id, " +
          "project.order.name, project.order.customer.id, project.order.customer.name) " +
          "FROM  Project project")
List<Project> findAllMinimal();

@Query("SELECT DISTINCT new de.projectemployee.ProjectEmployee(projectEmployee.id, " +
          "projectEmployee.employee.id, projectEmployee.employee.email, " +
          "projectEmployee.employee.firstName, projectEmployee.employee.lastName, " +
          "projectEmployee.employee.address, projectEmployee.employee.weeklyHoursEnabled, " +
          "projectEmployee.employee.weeklyHours, projectEmployee.employee.isArchived, " +
          "projectEmployee.employee.archivedDate, projectEmployee.project.id, projectEmployeeRole.id, " +
          "projectEmployeeRole.name, projectEmployeeRole.hourlyWage) FROM ProjectEmployee projectEmployee " +
          "LEFT JOIN projectEmployee.projectEmployeeRole projectEmployeeRole " +
          "WHERE projectEmployee.project IN :projects")
  List<ProjectEmployee> findByProjects(@Param("projects") List<Project> projects);

要给每个项目他的projectEmployees我需要一些附加的Java代码:

    List<Project> projects = projectRepository.findAllMinimal();
    List<ProjectEmployee> projectEmployees = projectEmployeeRepository.findByProjects(projects);
    Map<Long, List<ProjectEmployee>> projectIdToProjectEmployeesMap = new HashMap<>();
    for (ProjectEmployee projectEmployee : projectEmployees) {
      List<ProjectEmployee> projectEmployeesToBeSaved = projectIdToProjectEmployeesMap.getOrDefault(projectEmployee.getProject().getId(), new ArrayList<>());
      projectEmployeesToBeSaved.add(projectEmployee);
      projectIdToProjectEmployeesMap.put(projectEmployee.getProject().getId(), projectEmployeesToBeSaved);
    }
    projects.forEach(project -> project.setProjectEmployees(projectIdToProjectEmployeesMap.get(project.getId())));
    return projects;

所以,正如您所见,您无法实现我的目标,即以恒定数量的查询(2)来获取所有项目及其projectEmployees,并且仅选择我需要的字段。缺点是我有一个运行O(n)复杂性的Java代码。但是我将正在传输的数据量减少了90%以上,所以我认为它的价值。

很难相信需要像我使用的Java代码算法才能找到解决我的问题的方法,因此,如果有人找到了能够完成上述任务的更好的解决方案(仅使用sql查询,请分享。

java spring hibernate jpql
1个回答
0
投票

尚未针对您的情况进行验证,但是乍一看,您可能会利用一级缓存。执行后

© www.soinside.com 2019 - 2024. All rights reserved.