我具有以下实体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查询,请分享。
尚未针对您的情况进行验证,但是乍一看,您可能会利用一级缓存。执行后