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

Spring Data JPA, REST и Kotlin: подключение к БД

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

14 марта 2023

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

Содержание

  1. Интеграция с базой данных
  2. Репозиторий
  3. Сортировка стран по алфавиту
  4. Постраничный вывод

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

Интеграция с базой данных

Теперь пришла пора подключиться к базе данных и научиться считывать данные из неё. Сначала создадим таблицу country и наполним её данными:

create table country
(
    id serial constraint country_pk primary key,
    name varchar not null,
    population integer not null
);
insert into country (id, name, population) values (1, 'Китай', 1452371069);
insert into country (id, name, population) values (2, 'США', 333449281);
insert into country (id, name, population) values (3, 'Россия', 146030881);
insert into country (id, name, population) values (4, 'Германия', 83349300);
insert into country (id, name, population) values (5, 'Франция', 68959599);
insert into country (id, name, population) values (6, 'Италия', 59236213);

В результате выполнения этих запросов наша таблица будет выглядеть так:

Содержимое таблицы country

Затем в проекте в папке resources переименуем файл application.properties в application.yml и пропишем там настройки подключения к БД (здесь вам нужно указать свои настройки):

spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/example_db
    username: example_user
    password: "Pa$$word"
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQL10Dialect
        show_sql: true
        temp:
          use_jdbc_metadata_defaults: false

driver-class-name – это класс драйвера вашей СУБД, он берётся как раз из зависимости postgres. url, username и password указываете свои собственные. В секции jpa.properties.hibernate можно указать некоторые дополнительные параметры. Например, show_sql позволяет отображать в логах sql-запросы, которые будет генерить Spring Data.

Теперь раскомментируем ранее закомментированные нами зависимости spring-boot-starter-data-jpa и org.postgresql:postgresql в файле build.gradle.kts.

Репозиторий

Создадим сущность CountryEntity, поля которой будут мапиться на поля таблицы country в базе данных.

@Entity
@Table(name = "country")
class CountryEntity(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Int = 0,
    var name: String = "",
    var population: Int = 0,
)

Аннотация @Entity указывает на то, что перед нами именно сущность, жизненный цикл которой управляется Spring Data. Аннотация @Table в явном виде позволяет задать название таблицы, с которой связана эта сущность. Если имя таблицы и имя сущности совпадают, данную аннотацию вообще можно опустить.

Обратите внимание, что мы создаём обычный class, а не data class. Это связано с внутренней реализацией Spring Data и неизменяемые data-классы нам здесь не подходят. По той же причине все поля, которые могут менять свои значения, помечаются как var.

Аннотация @Id указывает поле, которое определяет уникальность сущности. @GeneratedValue указывает на способ присвоения значений этому идентификатору. В данном случе у нас автоинкремент, реализуемый средствами СУБД.

Перейдём к слою репозитория и создадим интерфейс CountryRepository. Наследуем его от стандартного интерфейса CrudRepostitory, который типизируется нашей сущностью и типом Int (тип поля id в сущности).

interface CountryRepository : CrudRepository<CountryEntity, Int>

И здесь начинается самое интересное, поскольку нам не нужно писать реализацию интерфейса. Spring Data сам сгенерит реализацию и sql-запросы под нашу конкретную СУБД автоматически!

Нам остаётся только внедрить этот интерфейс в сервисный слой и вызвать у него метод findAll() вместо нашего хардкода:

@Service
class CountryServiceImpl(
    private val countryRepository: CountryRepository,
) : CountryService {
    override fun getAll(): List<CountryDto> =
        countryRepository.findAll()
            .map { it.toDto() }

    private fun CountryEntity.toDto(): CountryDto =
        CountryDto(
            id = this.id,
            name = this.name,
            population = this.population,
        )
}

С помощью метода расширения CountryEntity.toDto() осуществляем преобразование списка сущностей базы данных в список dto, который отдаём как результат работы сервиса. На первый взгляд это может показаться избыточным, но с ростом количества полей у сущности часть из них может потребоваться скрыть от внешних пользователей. Или наоборот, добавить в dto какие-то дополнительные поля, вычисляемые динамически.

Теперь запустим наш проект и выполним тот же http-запрос, что делали ранее. В ответе мы увидим не хардкод, а именно те сущности, которые лежат в таблице country.

Сортировка стран по алфавиту

Если вы внимательно посмотрите на тот список, который пришёл нам в ответ, вы увидите, что его сортировка произвольна. Если же мы хотим задать явную сортировку, например, по названию, то нам достаточно определить в интерфейсе репозитория новый метод, следуя определённым соглашениям об именовании.

В данном случае его нужно назвать findByOrderByName(). Более подробно про соглашения об именовании читайте в статье Написание запросов в Spring Data JPA.

interface CountryRepository : CrudRepository<CountryEntity, Int> {
    fun findByOrderByName(): List<CountryEntity>
}

И в сервисном слое заменить вызов метода findAll().

override fun getAll(): List<CountryDto> =
    countryRepository.findByOrderByName()
        .map { it.toDto() }

Теперь снова запустим проект и убедимся, что страны отсортированы в алфавитном порядке.

Постраничный вывод

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

В метод репозитория добавим параметр типа Pageable.

fun findByOrderByName(pageable: Pageable): List<CountryEntity>

Очень важно, чтобы при постраничном выводе у вас была хоть какая-то сортировка. В противном случае для одной и той же страницы вы можете получать разные результаты.

В метод сервисного слоя добавим параметр pageIndex.

override fun getAll(pageIndex: Int): List<CountryDto> =
    countryRepository.findByOrderByName(PageRequest.of(pageIndex, 2))
        .map { it.toDto() }

В репозиторий мы передаём объект PageRequest, который является реализацией интерфейса Pageable. Метод PageRequest.of() принимает два параметра: номер страницы (0 – первая страница) и количество элементов на странице.

В метод контроллера также добавляем параметр pageIndex:

@GetMapping
fun getAll(@RequestParam("page") pageIndex: Int): List<CountryDto> =
    countryService.getAll(pageIndex)

Аннотация @RequestParam связывает его с параметром запроса page.

Теперь запускаем наш проект и, чтобы получить список стран на первой странице, выполняем следующий запрос:

curl http://127.0.0.1:8080/countries?page=0

Увеличивая параметр page мы постепенно можем пройтись по всем страницам. И на каждой будет не более двух стран.

В следующей статье Spring Data JPA, REST и Kotlin: поиск записей мы научимся искать страны по id и по названию.


Облако тэгов

Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.

Последние статьи


Комментарии

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

×

devmark.ru