angle-left

Оператор type в JPQL

В этой короткой заметке расскажу о том как открыл для себя новый оператор в JPQL.

При разработке нового функционала (а точнее после глобального рефакторинга и переделки) возинкла следующая ситуация. Есть @Entity организация от которой наследуется несколько других типов сущностей. Например, назовём их Bakery, Restaurant, BarberShop. Ниже пример кода класса организации и одного из его наследников.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Organization  {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    protected Long id;
   
    @NotNull
    protected String name;

}

 

@Entity
public class BarberShop extends Organization {
      //Some specific fields
}

Как видно из кода,  обычный пример использования наследования в JPA. Далее у меня возникла задача реализовать интерфейс, содержащий таблицу всех организаций. Выводить в таблице нужно было только тип организации и поля из базового класса. В проекте использовалась Spring Data. Поэтому метод получения из БД списка организаций был невероятно простой.

    List<Organization> findAll();

Если бы пришлось писать на JPQL, то всё было бы тоже очень просто

SELECT o FROM Organization o

Этот запрос бы делал автоматически join с нужными дочерними таблицами (такая выбрана у меня стратегия наследования в JPA) и возвращал бы уже объекты конкретных типов (Bakery Restaurant, BarberShop). Когда мне нужно было показать, что данная строка в таблице относится к какому-то определенному подтипу, то я просто использовал instanceOf. Это всё работало хорошо до тех пор, пока передо мной не встала задача добавить в эту таблицу фильтр по типу сущности. К тому моменту мною уже была прикручена пагинация и, очевидно, что никаких фильтраций на стороне приложения делать не хотелось. Плюсом попросили добавить ещё пару колонок в таблицу, которые были бы вычислимыми (например, нужно выводить кол-во сотрудников в организации). 

У меня было два решения этой задачи. Первый - глупый. Взять и написать switch-case по фильтру. В зависимости от значения фильтра, делать запрос к нужному репозиторию соответствующего подтипа. Такой вариант мне очень не понравился. Плюс так как есть возможность показывать все организации, а запрос на их получения теперь должен возвращать DTO, то непонятно как теперь показывать в этому случае для каждой записи в таблице, что это за организация. Конечно есть вариант, при формировании DTO написать в JPQL switch-case, но он тоже казался жутко нелогичным. У меня была стойкая уверенность, что есть в JPQL что-то, что позволит мне решить мою проблему. Магическая фраза, которая позволит в SpringDataJPA в репозитории базового Organization сделать запрос указав, какой подтип вернуть. 

 

 Как оказалось, решение есть. Оно просто и элегантное. Зовут его оператор TYPE. Этот прекрасный оператор позволяет вернуть тип Entity. Использовать его можно и в части SELECT, чтобы вернуть  нужный тип внутри DTO, и в части WHERE чтобы выбрать сущности только определенного подкласса. Небольшой пример того как выглядят запросы с использованием этого оператора.

@Query(value = "SELECT new ru.edu.portfolio.domain.dto.admin.OrganizationDTO(o.id, o.name,   o.employments.size, type(o)) " +
            "FROM Organization o WHERE TYPE(o) = :subClass"
    )
    Page<OrganizationDTO> findAllByType(@Param("subClass") Class<?> subClassType, Pageable pageable);

 

Также у меня возник вопрос в каком виде принимать внутри конструктора DTO результат выполнения оператора TYPE(o). Ответ оказался такой, как я предполагал. Вернётся тип: Class<?>. Таким вот образом я открыл для себя оператор TYPE. Советую вам тоже им пользоваться.

На этом, пожалуй, откланяюсь!