У меня есть эти (минимальные и частичные) объекты JPA:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public abstract class Employee implements Serializable {
@Id
protected Long id;
...
}
@Entity
...
public class FullTimeEmployee extends Employee implements Serializable {
private BigDecimal salary;
...
}
@Entity
...
public class PartTimeEmployee extends Employee implements Serializable {
private BigDecimal hourlyWage;
private BigDecimal maxHoursWeek;
...
}
В настоящее время мы используем spring-data-jpa для запроса, например:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
List<Employee> findAll();
}
Но, таким образом, у нас есть N + 1 проблем и много выборок, поэтому я решаю использовать Criteria API и выбираю его в DTO, например:
public List<EmployeeDTO> findAll() {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeDTO> criteriaQuery = criteriaBuilder.createQuery(EmployeeDTO.class);
Root<Employee> root = criteriaQuery.from(Employee.class);
Root<FullTimeEmployee> fullTimeEmployeeRoot = criteriaBuilder.treat(root, FullTimeEmployee.class);
Root<PartTimeEmployee> partTimeEmployeeRoot = criteriaBuilder.treat(root, PartTimeEmployee.class);
criteriaQuery.select(criteriaBuilder.construct(EmployeeDTO.class,
root.get("id"), root.get("name"),
fullTimeEmployeeRoot.get("salary"),
partTimeEmployeeRoot.get("hourlyWage"))
);
return this.entityManager
.createQuery(criteriaQuery).getResultList();
}
А это наш (пример) DTO
@Getter
@Setter
@AllArgsConstructor
public class EmployeeDTO {
private Long id;
private String name;
private BigDecimal fullTimeEmployeeSalary;
private BigDecimal partTimeEmployeeHourlyWage;
private BigDecimal partTimeEmployeeMaxHoursWeek;
...
}
Но мы получили 0 результатов.
Наш вывод гибернации выглядит так:
SELECT employee.id, employee.name, fullTimeEmployee.salary, partTimeEmployee.hourlyWage partTimeEmployee.maxHoursWeek ... FROM employees employee INNER JOIN fullTimeEmployees fullTimeEmployee on fullTimeEmployee.id = employee.id INNER JOIN partTimeEmployees partTimeEmployee on partTimeEmployee.id = employee.id
Мой вопрос: как лучше всего это сделать? Как преобразовать эти ВНУТРЕННИЕ СОЕДИНЕНИЯ в ЛЕВЫЕ СОЕДИНЕНИЯ? Есть способ получше?
Спасибо. :)
Ответ прост, поэтому вам все равно придется запрашивать и извлекать каждое отдельное поле для каждой сущности только для вычисления суммы заработной платы, где это можно сделать напрямую с помощью запроса. Как вы и просили изначально, вы можете изменить тип JOIN с помощью CriteriaAPI, используя второй параметр .join (): .join("yourField", JoinType.LEFT).




Сначала позвольте мне поблагодарить вас за очень красиво оформленный вопрос - вы проделали большую работу, создав минимальный, полный и проверяемый пример. Я не думаю, что вы хотите проецировать результат на такой класс, который вы описываете. Наличие класса со значением salary или hourlyWage означает, что вы постоянно проверяете наличие нуля, и это довольно плохое дизайнерское решение. Лучше получить список различных типов из employeeRepository и использовать объектно-ориентированные принципы для обработки смешанных типов. Именно для этого и было изобретено ООП.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public abstract class Employee implements Serializable {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
public abstract BigDecimal getPay();
@Entity
public class FullTimeEmployee extends Employee {
private BigDecimal salary;
private int daysWorked;
@Override
public BigDecimal getPay() {
return salary
.multiply(BigDecimal.valueOf(daysWorked))
.divide(BigDecimal.valueOf(Year.now().length()), RoundingMode.HALF_DOWN);
}
@Entity
public class PartTimeEmployee extends Employee {
private BigDecimal hourlyWage;
private int hoursWorked;
@Override
public BigDecimal getPay() {
return hourlyWage.multiply(BigDecimal.valueOf(hoursWorked));
}
а потом
BigDecimal sum = employeeRepo.findAll()
.stream()
.map(e->e.getPay())
.reduce(BigDecimal.ZERO, BigDecimal::add);
Привет, К.Николай, спасибо за помощь! Это хорошее решение ... Но что произойдет, если мне понадобится другой атрибут, который существует только в одной из сущностей, например: maxHoursWeek? Мне нужно будет создавать все эти методы рефератов? Спасибо
Да, это всегда хороший вопрос. Лично я бы подумал о разделении обработки на почасовых и зарплатных сотрудников. Вы просто получаете список сотрудников по окладам из FullTimeEmployeeRepository и отдельный список сотрудников с почасовой оплатой из PartTimeEmployeeRepository и обрабатываете эти два списка отдельно. Различий достаточно, что в реальном мире было бы безумием смешивать их, по крайней мере, для обработки. Возможно, было бы разумно иметь такое наследование в базе данных, но было бы редко получать только сотрудников.
PS> «Но что произойдет, если мне понадобится другой атрибут, который существует только в одной из сущностей, например: maxHoursWeek?» -> именно для этого был изобретен шаблон декоратора.
К.Николас, еще раз спасибо за помощь, извините, я не могу проголосовать за ваш ответ, у меня пока нет репутации.
Что ж, попытка сделать это - потеря информации. Почему не только
employeeRepository.findAll()? Это даст вам список со смешанными FTE и PTE, и вы просмотрите список и вызовете метод в зависимости от типа экземпляра. То, что вы хотите, дает вам ноль в одном или другом поле, что просто уродливо, потому что вам придется постоянно проверять наличие нулей. Еще лучше, если бы каждый конкретный класс реализовал интерфейс, и вам даже не нужно было бы проверять, какой тип находится в списке. Еще лучше использовать шаблон декоратора.