Как получить из сущности только нужные поля без создания кастомной сущности в Java
Что проверяют
Знаете ли вы, как не тащить из базы всю сущность ради двух колонок, и понимаете ли разницу между проекцией на уровне SQL и фильтрацией уже загруженных объектов. Следом спрашивают, какой SQL реально уйдёт в базу и можно ли такую проекцию изменить и сохранить.
Как отвечать
«Без кастомной сущности» значит без нового @Entity, а лёгкий интерфейс или record для результата вполне допустимы. Варианты:
- интерфейсная проекция Spring Data:
interface UserView { String getUsername(); String getEmail(); }и методList<UserView> findByActiveTrue(). Для производного метода с закрытой проекцией, где геттеры совпадают с полями, Spring Data выбирает только эти колонки; - DTO через конструктор:
select new com.example.UserDto(u.username, u.email) from User uв JPQL, в роли DTO удобно братьrecord; Tuple:em.createQuery(jpql, Tuple.class)с перечислением полей вselect, значения достаются по алиасу. Новых классов нет вовсе, но типы приводятся вручную;- динамическая проекция:
<T> List<T> findByActiveTrue(Class<T> type), тип результата выбирает вызывающий код; - Criteria API:
cb.createTupleQuery()иmultiselectдля запросов, которые собираются на лету.
Главное, что стоит сказать: проекция возвращает не управляемые сущности. Их нельзя поменять и сохранить через save, зато persistence context не раздувается и нет dirty checking.
Результат лучше подтвердить: включить логгер org.hibernate.SQL и убедиться, что в select ушли только нужные колонки.
Где спотыкаются
Загружают сущности целиком, а потом делают stream().map(...) в DTO. Это преобразование в памяти, в базу по-прежнему ушёл запрос за всеми колонками.
Используют открытую проекцию с @Value и выражением SpEL вроде #{target.username + ' ' + target.email}. Spring Data в этом случае загружает всю сущность, и выигрыша нет.
Пишут пример с javax.persistence. Начиная со Spring Boot 3 пакет называется jakarta.persistence, и старый импорт выдаёт знание устаревшего стека.