Статьи

Spring Data Rest с примерами на kotlin

Вернуться назад Исходники

25 октября 2020

Тэги: rest gradle Spring Boot Spring SQL PostgreSQL Hibernate Spring Data Kotlin

Содержание

  1. Подключаем Spring Data Rest
  2. Маппинг полей таблицы
  3. Создание репозитория
  4. Просмотр записей
  5. Создание новой записи
  6. Редактирование записи
  7. Удаление записи
  8. Итоги

Ранее я уже приводил пример в статье CrudRepository на Kotlin, как Spring Data позволяет легко выполнять основные операции над сущностями в БД. Теперь пойдём ещё дальше и рассмотрим как Spring Data Rest позволяет избежать написания контроллеров и сервисной логики. Исходники тестового проекта также прилагаются к этой статье и доступны на github.

Подключаем Spring Data Rest

Для начала создадим заготовку проекта. Проще всего это сделать с помощью сайта start.spring.io. В настройках выбираем в качестве языка Kotlin и в качестве сборщика Gradle. В dependency нам нужно последовательно добавить три зависимости: Spring Data JPA, Rest Repositories и PostgreSQL Driver. В итоге файл build.gradle.kts должен содержать, помимо стандартных, следующие зависимости:

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-data-rest")
    runtimeOnly("org.postgresql:postgresql")
    // ...
}

Компонент spring-boot-starter-data-jpa отвечает за работу с БД и предоставляет аннотации для разметки сущностей в стиле Hibernate. spring-boot-starter-data-rest автоматически генерирует rest-контроллеры для доступа к данным. org.postgresql - драйвер СУБД (вы можете использовать другой). Обратите внимание, что в gradle он объявлен как runtimeOnly зависимость, т.к. для компиляции проекта драйвер не требуется. Мы объявим его в декларативном стиле в настройках проекта.

Теперь найдём в проекте файл настроек application.properties (пока он пустой) и переименуем его в application.yml, чтобы указывать настройки нашего приложения в yaml формате. Настройки можно указывать в любом формате, подробнее см. статью Сравнение форматов конфига в Spring Boot.

В файле настроек нам нужно указать параметры подключения к нашей БД postgres.

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

Вам в этих настройках надо заменить только url, username и password на свои собственные.

Маппинг полей таблицы

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

create table band
(
    id            serial      not null
        constraint band_pk
            primary key,
    name          varchar(50) not null,
    players_count integer     not null,
    created       date        not null
);

Замапим эту таблицу в data-класс на kotlin:

@Entity
// @Table(name = "band")
data class Band(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        val id: Int,
        val name: String,
        // @Column(name = "players_count")
        val playersCount: Int,
        val created: LocalDate
)

Аннотация @Entity, как вы знаете из прошлой статьи, определяет, что перед нами класс, поля которого мапятся на поля таблицы. По имени этого класса автоматически выводится имя таблицы. Если имя таблицы отличается от имени класса, то её имя можно указать с помощью аннотации @Table. Идентификатор записи, по которому Spring Data определяет уникальность, задаём с помощью аннотации @Id. Поскольку для каждой новой записи это значение автоматически инкрементируется (увеличивается на 1) средствами БД, также надо указать аннотацию @GeneratedValue. Остальные поля, аналогично таблице, мапятся по своим именам. Причём имя с нижним подчёркиванием (т.н. «snake case») автоматически преобразуется в привычный нам «camel сase». Если же имя колонки отличается от имени поля в классе, можно использовать для явного указания имени аннотацию @Column.

Создание репозитория

Теперь создадим репозиторий, который будет работать с этой сущностью. Причём он одновременно будет предоставлять функциональность также и rest-контроллера. И для этого нам потребуется лишь одна аннотация и один интерфейс (даже без реализации).

@RepositoryRestResource(path = "bands")
interface BandRepository: PagingAndSortingRepository<Band, Int>

Аннотация @RepositoryRestResource определяет, что этот интерфейс является и репозиторием, и rest-контроллером. Сам репозиторий наследуется от интерфейса PagingAndSortingRepository и типизируется нашей сущностью Band. Этот базовый интерфейс автоматически добавляет функционал сортировки и постраничного вывода записей. Если вам такой функционал не нужен, можете наследоваться от более базового CrudRepository.

Собственно говоря, это всё, что нужно написать в коде. Теперь запускаем приложение.

Просмотр записей

Если мы выполним GET-запрос по адресу 127.0.0.1:8080, то в ответ получим следующий json:

{
    "_links": {
        "bands": {
            "href": "http://127.0.0.1:8080/bands{?page,size,sort}",
            "templated": true
        },
        "profile": {
            "href": "http://127.0.0.1:8080/profile"
        }
    }
}

Это специальный формат HATEOAS, который помимо самих данных содержит также и ссылки на них, что удобно для отображения на frontend. В данном случае мы видим, что у нас имеется эндпоинт /bands c тремя опциональными параметрами page, size и sort.

Чтобы получить список всех записей, выполним GET-запрос по урлу 127.0.0.1:8080/bands. Если у нас не пустая таблица, то ответ будет примерно таким:

{
    "_embedded": {
        "bands": [
            {
                "name": "Queen",
                "playersCount": 4,
                "created": "1973-07-13",
                "_links": {
                    "self": {
                        "href": "http://127.0.0.1:8080/bands/1"
                    },
                    "band": {
                        "href": "http://127.0.0.1:8080/bands/1"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://127.0.0.1:8080/bands"
        },
        "profile": {
            "href": "http://127.0.0.1:8080/profile/bands"
        }
    },
    "page": {
        "size": 20,
        "totalElements": 5,
        "totalPages": 1,
        "number": 0
    }
}

Тут мы видим все поля каждой записи в таблице, ссылки на каждую запись отдельно (по сути просто к bands добавляется id записи) и в конце списка общую информацию о том, сколько всего элементов, сколько страниц, каков размер страницы и её номер (начинается с нуля). Если мы хотим выводить по две записи на страницу и отобразить вторую страницу, то следует выполнить такой GET-запрос: 127.0.0.1:8080/bands?page=1&size=2.

Для просмотра отдельной записи просто переходим по нужной ссылке. Например, если запись имеет id = 2, то выполняем GET-запрос 127.0.0.1:8080/bands/2. В ответ получим примерно следующее:

{
    "name": "Scorpions",
    "playersCount": 5,
    "created": "1965-01-01",
    "_links": {
        "self": {
            "href": "http://127.0.0.1:8080/bands/2"
        },
        "band": {
            "href": "http://127.0.0.1:8080/bands/2"
        }
    }
}

Создание новой записи

Если мы хотим добавить в таблицу новую запись, то нужно выполнить POST-запрос на урл 127.0.0.1:8080/bands с указанием в теле запроса всех полей, кроме id. Также не забудьте передать заголовок «Content-Type: application/json». В нашем примере запрос может выглядеть так:

{
    "name": "Beatles",
    "playersCount": 4,
    "created": "1960-01-01"
}

id новой записи можно узнать, если повторно посмотреть список всех записей 127.0.0.1:8080/bands.

Редактирование записи

Если мы хотим отредактировать одно или несколько полей, не обязательно указывать запись целиком. Достаточно передать только изменяемые поля. Например, если хотим отредактировать количество участников и название, просто передаём эти значения в соответствующих полях через PATCH-запрос на урл конкретной записи 127.0.0.1:8080/bands/2:

{
    "name": "Scorpions mod",
    "playersCount": 42
}

Удаление записи

Для удаления записи достаточно кинуть на урл конкретной записи DELETE-запрос. В нашем примере это урл 127.0.0.1:8080/bands/2.

Итоги

Как видите, Spring Data Rest позволяет обеспечить полный набор операций по работе с записями в БД при минимальных трудозатратах на написание кода. Данный подход не стоит использовать в промышленных системах, т.к. там логика бывает гораздо сложнее и требует более гибкой настройки, но для быстрого создания прототипов это самое оптимальное решение. Ведь кому захочется вручную писать всю логику CRUD-операций для нескольких десятков сущностей?