Способы получения проекций в Hibernate

Иногда требуется получить из базы не все поля сущности, а выборочно.

Например, есть шаблон проектирования DTO (Data Transfer Object) — суть его в том, что мы создаем специальный класс с небольшим количеством полей для отправки на фронтенд. К примеру, есть сущность City c 20 полями, а мы создаем для нее класс CityDto, который имеет всего два поля: id и name.
Проекции — это понятие JPA, которое и означает частично извлекаемые данные. Извлечь данные частично можно разными способами. Ниже рассмотрим примеры.

Модель

Допустим, у нас есть сущность City с тремя полями (можно больше):

@Data
@NoArgsConstructor
@Entity
public class City {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
    private String name;
    private String code;

}

А извлечь из базы нам надо только два поля, и для этого у нас есть Data Transfer Object — класс CityDto:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CityDto {
    private long id;
    private String name;
}

Constructor Expression

Первый способ получить CityDto — это вызвать его конструктор прямо в JPQL-запросе, только путь до класса нужно указывать полностью — ru.sysout.dto.CityDto:

List<CityDto> cityDtos = em.createQuery("select new ru.sysout.dto.CityDto(c.id, c.name) from City c", CityDto.class)
                .getResultList();

Так мы получили список CityDto.

С помощью Tuple

Еще способ — использовать универсальный класс Tuple. В этом случае не надо создавать Data Transfer Object, но извлекать из Tuple конкретные поля не так удобно.

List<Tuple> cityDtos = em.createQuery("select c.id as id, c.name as name from City c", Tuple.class)
                .getResultList();
Assertions.assertEquals("name1", cityDtos.get(0).get("name"));

Например, название name извлекается как tuple.get(«name»).

Tuple и Native Query

Использовать Tuple можно и с нативным SQL-запросом (а не только с JPQL):

List<Tuple> cityDtos = em.createNativeQuery("select c.id as id, c.name as name from City c", Tuple.class)
                .getResultList();

С помощью ResultTransformer

Наконец, существует интерфейс ResultTransformer, который можно реализовать самостоятельно. В следующей статье так и сделаем, а пока  преобразуем результат запроса в CityDto с помощью готовой реализации ResultTransformer:

List<CityDto> cityDtos = em.createQuery("select c.id as id, c.name as name from City c")
          .unwrap(org.hibernate.query.Query.class)
          .setResultTransformer(Transformers.aliasToBean(CityDto.class))
          .getResultList();

Вообще ResultTransformer — самый гибкий способ.

Исходный код

Мы рассмотрели, что такое проекция в JPA и как ее получить.

В следующей статье рассмотрим проекцию OneToMany (CityDto будет содержать коллекцию районов DistrictDto).

Общая статья по проекциям в Spring тут.

Код примера доступен на GitHub.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *