Статьи

Spring Boot и Spring Data REST — отображение репозиториев поверх REST

Логотип-весна-ю

Открывать репозитории Spring Data через REST довольно просто с Spring Boot и Spring Data REST. С минимальным кодом можно создавать REST-представления объектов JPA, которые следуют принципу HATEOAS . Я решил повторно использовать JPA-сущности Spring PetClinic (бизнес-уровень) в качестве основы для этой статьи.

Основа приложения

Модель PetClinic относительно проста, но она состоит из однонаправленных и двунаправленных ассоциаций, а также базового наследования:

sbdr-petclinic-схема,

Кроме того, Spring PetClinic предоставляет сценарии SQL для HSQLDB, что делает создание схемы и заполнение ее образцами данных в моем новом приложении очень простым.

Зависимости проекта

В качестве основы для конфигурации я использовал Spring Initializr и создал базовый проект Gradle. Чтобы использовать Spring Data REST в приложении Spring Boot, я добавил следующие загрузчики:

1
2
3
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-data-rest")

Кроме того, я добавил зависимость HSQLDB в проект:

1
compile("org.hsqldb:hsqldb:2.3.2")

Исходный проект использует org.joda.time.DateTime для полей даты и использует org.jadira.usertype.dateandtime.joda.PersistentDateTime что позволяет сохранять его в Hibernate. Чтобы использовать его в новом проекте, мне нужно было добавить следующие зависимости:

1
2
compile("joda-time:joda-time:2.4")
compile("org.jadira.usertype:usertype.jodatime:2.0.1")

Работая с API, я заметил, что хотя поля date в исходном проекте были аннотированы с помощью Spring @DateTimeFormat они не были должным образом сериализованы. Я обнаружил, что мне нужно использовать @JsonFormatter , поэтому в build.gradle была добавлена ​​еще одна зависимость:

1
compile("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.4.2");

Оказавшись в пути к классам, Spring Boot автоматически настраивает com.fasterxml.jackson.datatype.joda.JodaModule через org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration .

Обратите внимание, что если вы хотите правильно сериализовать типы даты и времени Java 8, вам нужно добавить в проект зависимость типа данных JSON310 от Jackson .

Инициализация базы данных

Для инициализации источника данных я добавил файлы schema-hsqldb.sql и data-hsqldb.sql в src/main/resources . Наконец, я добавил несколько свойств в application.properties :

1
2
3
spring.datasource.platform = hsqldb
   spring.jpa.generate-ddl = false
   spring.jpa.hibernate.ddl-auto = none

Теперь при запуске приложения файлы будут автоматически выбраны, а источник данных будет инициализирован, и обнаружение API будет намного проще, поскольку есть данные!

Хранилища

Основная идея Spring Data REST заключается в том, что он построен на основе репозиториев Spring Data и автоматически экспортирует их как ресурсы REST . Я создал несколько репозиториев, по одному для каждой сущности ( OwnerRepository , PetRepository и т. Д.). Все репозитории являются интерфейсами Java, расширяющимися из PagingAndSortingRepository .

На этом этапе не @Controller никакого дополнительного кода: нет @Controller s, нет конфигурации (если не требуется настройка). Spring Boot автоматически настроит все для нас.

Запуск приложения

При наличии всей конфигурации проект может быть выполнен (вы найдете ссылку на весь проект внизу статьи). Если вам повезет, приложение запустится, и вы сможете перейти по http://localhost:8080 который указывает на коллекцию ссылок на все доступные ресурсы ( корневой ресурс ). Тип содержимого ответа:

HAL

Ресурсы реализованы в стиле Hypermedia, и по умолчанию Spring Data REST использует HAL с типом контента application/hal+json для визуализации ответов. HAL — это простой формат, который позволяет легко связывать ресурсы. Пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
$ curl localhost:8080/owners/1
{
  "firstName" : "George",
  "lastName" : "Franklin",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/owners/1"
    },
    "pets" : {
    }
  }
}

С точки зрения Spring Data REST, существует несколько типов ресурсов: коллекция, элемент, поиск, метод запроса и связь, и все они используют тип содержимого application/hal+json в ответах.

Коллекционный и предметный ресурс

Ресурсы коллекции поддерживают как методы GET и POST . Ресурсы элементов обычно поддерживают методы GET , PUT , PATCH и DELETE . Обратите внимание, что PATCH применяет значения, отправленные с телом запроса, тогда как PUT заменяет ресурс.

Поиск и поиск метода ресурса

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

Чтобы визуализировать это, я добавил метод find в OwnerRepository :

1
List<Owner> findBylastName(@Param("lastName") String lastName);

Который затем был выставлен в http://localhost:8080/owners/search :

1
2
3
4
5
6
7
8
9
$ curl http://localhost:8080/owners/search                                    
{                                                                             
  "_links" : {                                                                
    "findBylastName" : {                                                      
      "href" : "http://localhost:8080/owners/search/findBylastName{?lastName}",
      "templated" : true                                                      
    }                                                                         
  }                                                                           
}

Ресурс ассоциации

Spring Data REST предоставляет субресурсы автоматически. Ресурс ассоциации поддерживает методы GET , POST и PUT .

и позволить управлять ими. При работе с ассоциацией вы должны знать тип содержимого text / uri-list . Запросы с этим типом содержимого содержат один или несколько URI ( каждый URI должен отображаться в одной и только одной строке ) ресурса для добавления в ассоциацию.

В первом примере мы рассмотрим однонаправленное отношение в классе Vet :

1
2
3
4
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
        inverseJoinColumns = @JoinColumn(name = "specialty_id"))
private Set<Specialty> specialties;

Чтобы добавить существующие специальности в коллекцию специальностей ветеринара, необходимо выполнить запрос PUT :

1
curl -i -X PUT -H "Content-Type:text/uri-list" -d $'http://localhost:8080/specialties/1\nhttp://localhost:8080/specialties/2' http://localhost:8080/vets/1/specialties

Удаление ассоциации может быть сделано с помощью метода DELETE как DELETE ниже:

1
curl -i -X DELETE http://localhost:8080/vets/1/specialties/2

Давайте посмотрим на другой пример:

1
2
3
4
5
6
7
8
// Owner
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Pet> pets;
 
// Pet
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "owner_id")
private Owner owner;

Установка владельца домашнего животного может быть сделана с помощью запроса ниже:

1
curl -i -X PUT -H "Content-Type:text/uri-list" -d "http://localhost:8080/owners/1" http://localhost:8080/pets/2/owner

Но как насчет удаления владельца? Так как владелец должен быть всегда установлен для питомца, мы получаем HTTP/1.1 409 Conflict , пытаясь сбросить его с помощью следующей команды:

1
curl -i -X DELETE http://localhost:8080/pets/2/owner

Интеграционные тесты

Spring Boot позволяет запускать веб-приложение в тесте и проверять его с помощью @IntegrationTest Spring Boot. Вместо использования MockMvc контекста веб-приложения на стороне сервера ( MockMvc ) мы будем использовать RestTemplate и его реализацию Spring Boot для проверки реальных вызовов REST.

Как мы уже знаем, ресурсы относятся к типу контента application/hal+json . Таким образом, фактически невозможно будет десериализовать их непосредственно в объект сущности (например, Owner ). Вместо этого он должен быть десериализован в org.springframework.hateoas.Resource который оборачивает сущность и добавляет к ней ссылки. А поскольку Resource является универсальным типом, необходимо использовать RestTemplate с RestTemplate .

В приведенном ниже примере показано, что:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private RestTemplate restTemplate = new TestRestTemplate();
 
@Test
public void getsOwner() {
    String ownerUrl = "http://localhost:9000/owners/1";
 
    ParameterizedTypeReference<Resource<Owner>> responseType = new ParameterizedTypeReference<Resource<Owner>>() {};
 
    ResponseEntity<Resource<Owner>> responseEntity =
            restTemplate.exchange(ownerUrl, GET, null, responseType);
 
    Owner owner = responseEntity.getBody().getContent();
    assertEquals("George", owner.getFirstName());
 
    // more assertions
 
}

Этот подход хорошо описан в следующей статье: Использование службы отдыха Spring-hateoas с использованием токенов типа Spring RestTemplate и Super

Резюме

С помощью нескольких шагов и возможностей Spring Boot и Spring Data REST я создал API для существующей базы данных PetClinic. С Spring Data REST можно сделать гораздо больше (например, настройку), и, помимо довольно скудной документации, по сравнению с другими проектами Spring, кажется, что Spring Data REST может значительно ускорить разработку. На мой взгляд, это хороший проект, когда нужно быстро создавать прототипы.

использованная литература