У меня есть следующая сущность 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();
Это работает, но возвращает все свойства каждого отдельного объекта. Например, меня интересуют только идентификатор и имя ord.customer
, в этом случае мне не нужны все остальные поля ord.customer
. Проблема с получением всех полей таким способом состоит в том, что передается много данных, которые мне не нужны в этом случае. Чтобы выбрать только те, которые мне нужны, и уменьшить объем данных, которые я посылаю через inte rnet, я мог бы сделать что-то вроде этого:
@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
, я не думаю, что смогу передать все 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);
Чтобы дать каждому проекту свой проект. Мне нужен какой-то дополнительный 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;
Итак, вы, как вы видите, можете достичь Моя цель - собрать все проекты с их ProjectEmployees в постоянном количестве запросов (2) и выбрать только те поля, которые мне нужны. Недостатком является то, что у меня есть javacode, работающий со сложностью O (n). Но я уменьшил размер передаваемых им данных более чем на 90%, поэтому думаю, что это стоит.
Трудно поверить, что алгоритм кода java, подобный тому, который я использовал, необходим для поиска решение для моей проблемы, поэтому, если кто-то найдет лучшее решение (только с sql запросами), способное выполнить вышеизложенное, поделитесь им.