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