Статьи
Утилиты Telegram YouTube Отзывы

Spring Data JPA, REST и Kotlin: проекции

Видеогайд Исходники

3 мая 2023

Тэги: Collections, Kotlin, rest, Spring Boot, Spring Data, SQL, YouTube, руководство.

Содержание

  1. Список из названий стран
  2. Оптимизируем sql с помощью проекции
  3. Выводы

В предыдущей статье Spring Data JPA, REST и Kotlin: "один-ко-многим", изменение данных мы научились изменять дочерние сущности городов при изменении родительской сущности страны. А сейчас давайте научимся выбирать из таблицы только те поля, которые нам реально нужны, а не всю сущность целиком. В Spring Data JPA есть такое понятие как проекции. Рассмотрим конкретный пример.

Список из названий стран

Давайте сделаем новый эндпоинт, который будет возвращать список названий всех стран, отсортированный по алфавиту. Кроме названий, других полей выводить не требуется. Опираясь на уже имеющиеся у нас знания из предыдущих статей, добавим в CountryRepository такой метод:

fun findAllByOrderByName(): List<CountryEntity>

В CountryService также добавим новый метод, возвращающий обычный список строк:

fun getCountryNames(): List<String>

Его реализация в CountryServiceImpl будет выглядеть так:

override fun getCountryNames(): List<String> =
    countryRepository.findAllByOrderByName().map { it.name }

Здесь мы просто преобразуем исходный список CountryEntity в список названий.

Наконец, добавим новый метод в контроллер CountryController.

@GetMapping("/names")
fun getCountryNames(): List<String> = countryService.getCountryNames()

Запускаем приложение и выполняем в Postman GET-запрос на эндпоинт /countries/names.

Запрос списка стран в Postman

Как видим, эндпоинт полностью выполняет свою задачу. Но что происходит «под капотом» в Spring Data?

Оптимизируем sql с помощью проекции

Если после выполнения запроса мы заглянем в консоль, то увидим, что Spring Data генерирует sql для выборки всех полей, которые есть в таблице country, хотя нам нужно только одно из них. Конечно, в данном случае это не критично, но при больших выборках из таблиц на несколько десятков полей это может уже существенно повлиять на расход памяти. Как решить эту проблему?

Spring Data позволяет управлять генерацией sql при выборке данных с помощью проекций. Проекция – это подмножество полей исходной сущности. В нашем случае проекция должна содержать одно поле. Напомню, что исходная сущность выглядит так:

@Entity
@Table(name = "country")
class CountryEntity(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Int = 0,
    var name: String = "",
    var population: Int = 0,
    @OneToMany(mappedBy = "country")
    var cities: List<CityEntity> = emptyList(),
)

Обратите внимание, что мы тут не используем data class, а все поля имеют модификатор var. Теперь создадим новый класс NameOnly, который будет содержать точно такое же изменяемое поле name, как и в родительской сущности.

class NameOnly (
    var name: String,
)

Теперь просто заменим в сигнатуре репозитория CountryRepository стран исходную сущность на данный класс.

fun findAllByOrderByName(): List<NameOnly>

Запустим приложение, ещё раз выполним тот же запрос и убедимся, что в консоли теперь генерируется sql, содержащий ровно одно нужное нам поле.

Кстати, класс NameOnly никак не связан напрямую с CountryEntity, а потому может заменять любую сущность с полем name в любом репозитории вашего приложения.

Выводы

Мы рассмотрели простой пример, который наглядно демонстрирует, что Spring Data позволяет управлять генерацией sql в декларативном стиле с помощью проекций. При этом сам класс проекций обладает высокой степенью переиспользования.



Комментарии

17.12.2023 23:19 Семён

Пройдено, спасибо. но куда расти дальше?

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

×

devmark.ru