JPA - Как заполнить DTO с помощью критерияBuilder.construct из наследования сущности

У меня есть эти (минимальные и частичные) объекты 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

Мой вопрос: как лучше всего это сделать? Как преобразовать эти ВНУТРЕННИЕ СОЕДИНЕНИЯ в ЛЕВЫЕ СОЕДИНЕНИЯ? Есть способ получше?

Спасибо. :)

Что ж, попытка сделать это - потеря информации. Почему не только employeeRepository.findAll()? Это даст вам список со смешанными FTE и PTE, и вы просмотрите список и вызовете метод в зависимости от типа экземпляра. То, что вы хотите, дает вам ноль в одном или другом поле, что просто уродливо, потому что вам придется постоянно проверять наличие нулей. Еще лучше, если бы каждый конкретный класс реализовал интерфейс, и вам даже не нужно было бы проверять, какой тип находится в списке. Еще лучше использовать шаблон декоратора.

K.Nicholas 27.10.2018 03:31

Ответ прост, поэтому вам все равно придется запрашивать и извлекать каждое отдельное поле для каждой сущности только для вычисления суммы заработной платы, где это можно сделать напрямую с помощью запроса. Как вы и просили изначально, вы можете изменить тип JOIN с помощью CriteriaAPI, используя второй параметр .join (): .join("yourField", JoinType.LEFT).

Alex 13.03.2019 10:45
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
1 617
1

Ответы 1

Сначала позвольте мне поблагодарить вас за очень красиво оформленный вопрос - вы проделали большую работу, создав минимальный, полный и проверяемый пример. Я не думаю, что вы хотите проецировать результат на такой класс, который вы описываете. Наличие класса со значением 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? Мне нужно будет создавать все эти методы рефератов? Спасибо

João M 27.10.2018 20:26

Да, это всегда хороший вопрос. Лично я бы подумал о разделении обработки на почасовых и зарплатных сотрудников. Вы просто получаете список сотрудников по окладам из FullTimeEmployeeRepository и отдельный список сотрудников с почасовой оплатой из PartTimeEmployeeRepository и обрабатываете эти два списка отдельно. Различий достаточно, что в реальном мире было бы безумием смешивать их, по крайней мере, для обработки. Возможно, было бы разумно иметь такое наследование в базе данных, но было бы редко получать только сотрудников.

K.Nicholas 28.10.2018 02:38

PS> «Но что произойдет, если мне понадобится другой атрибут, который существует только в одной из сущностей, например: maxHoursWeek?» -> именно для этого был изобретен шаблон декоратора.

K.Nicholas 28.10.2018 23:58

К.Николас, еще раз спасибо за помощь, извините, я не могу проголосовать за ваш ответ, у меня пока нет репутации.

João M 29.10.2018 23:50

Другие вопросы по теме