Иногда требуется получить из базы не все поля сущности, а выборочно.
Например, есть шаблон проектирования 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.