Создание Spring Boot Data Rest CRUD-приложения

Создадим crud приложение, только теперь с использованием Spring Data Rest.

Создадим новое Spring Boot приложение с такими папками и файлами.

Как видите, в этом проекте, по сравнению с предыдущими crud приложениями которые мы разрабатывали, стало крайне мало файлов.

Нет дао класса, сервис класса и даже контроллера. А если вы пролистаете данный урок далее, то увидите, что в файлах вообще практически нет кода.

Search Icon

Но при всем этом, это полностью рабочее crud приложение. Что? Как такое возможно?

Давайте же разберемся.

Для начала, давайте добавим зависимость для поддержки Spring Data Rest.

4.0.0 org.springframework.boot spring-boot-starter-parent 3.1.5 com.SpringBootApps firstSpringBootDataRestCRUDApp 0.0.1-SNAPSHOT firstSpringBootDataRestCRUDApp Demo project for Spring Boot 17 org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web com.mysql mysql-connector-j runtime org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-devtools test org.springframework.boot spring-boot-starter-data-rest org.springframework.boot spring-boot-maven-plugin

В прошлом уроке мы задавали вопрос о DAO имплементациях. Нужно ли их так много?

Но тот же вопрос можно применить и к контроллерам! Нужно ли для каждой сущности писать маппинги(ну там GetMapping, PostMapping и т.д.) для CRUD операций?

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

Search Icon

Spring может сам создать эти методы за кулисами!

Всё что нам нужно, это создать интерфейсы расширяющие JpaRepository для сущностей, а спринг сам предоставит нам адреса, которые мы писали в GetMapping, PostMapping и т.д. по которым мы можем совершать CRUD операции. Адреса эти формируются автоматически по имени класса, который мы передали в JpaRepository<[По имени этого класса],Integer>.

Например, для JpaRepository<Actor, Integer> Spring Data Rest сгенерирует адреса /actors, /actors/{actorId} и т.д. для разных HTTP методов.

Также важно упомянуть что в JSON ответах от таких сгенерированных REST методов можно увидеть некоторые метаданные. Например, по какой ссылке был сделан запрос или количество переданных объектов и т.д.

package com.SpringBootApps.firstSpringBootDataRestCRUDApp.dao; import org.springframework.data.jpa.repository.JpaRepository; //Как уже было сказано адреса формируются автоматически по имени класса, //который мы передали в JpaRepository<[По имени этого класса],Integer>. public interface ActorRepository extends JpaRepository { }

Давайте же протестируем созданные обработчики.

В таблице actor сейчас три актера:

Давайте выберем из БД всех актеров GET запросом с помощью автоматически сгенерированного с помощью Spring Data Rest адреса /actors.

Как видим, все актеры успешно были получены.

При этом, как уже упоминалось, были присланы также некоторые метаданные. Например, можно увидеть, что JSON каждого актера содержит ссылку по которой можно получить доступ к нему.

Также на скриншоте снизу можно увидеть метаданные о страницах – количество актеров на странице (три), номер страницы (нулевая страница), размер страницы (на странице может быть всего 20 актеров). То есть актеры разбиваются на страницы. Пока сильно не будем углубляться, что такое страницы и как с ними взаимодействовать.

Давайте теперь выберем из БД одного конкретного актера GET запросом с помощью автоматически сгенерированного с помощью Spring Data Rest адреса /actors/{actorId}.

Как видим, актер с id два был успешно выбран из БД и прислан в ответ на GET запрос. Также с метаданными.

Давайте теперь добавим в БД нового актера POST запросом с помощью автоматически сгенерированного с помощью Spring Data Rest адреса /actors. Для этого пишем в body данные в формате JSON, которые мы хотим добавить в БД.

Выше видим, что в результате выполнения POST запроса был получен код успешного выполнения операции201. Ниже можно увидеть, что актер успешно добавился в БД.

Давайте теперь обновим в БД актера PUT запросом с помощью автоматически сгенерированного с помощью Spring Data Rest адреса /actors/{actorId}. Для этого пишем в body данные в формате JSON, которыми мы хотим обновить актера с id, который указывается в конце адреса.

Выше видим, что в результате выполнения PUT запроса был получен код успешного выполнения операции 201. Ниже можно увидеть, что актер с id четыре успешно обновился в БД.

Давайте теперь удалим из БД актера DELETE запросом с помощью автоматически сгенерированного с помощью Spring Data Rest адреса /actors/{actorId}. Для этого пишем в конце адреса id актера, которого мы хотим удалить.

Выше видим, что в результате выполнения DELETE запроса был получен код успешного выполнения операции201. Ниже можно увидеть, что актер с id четыре успешно удалился из БД.

Search Icon

Еще раз заметим. Все эти ссылки были фактически ПОДАРЕНЫ нам средствами Spring Data Rest. Это может серьезно сэкономить время если нам нужны простейшие crud операции.


Доп. конфигурация Spring Boot Data Rest приложения

А что если нам нужно чтобы Spring Data Rest не генерировал автоматически некоторые адреса для CRUD?

Что если мы хотим написать их код вручную в контроллере (например, для POST и DELETE)? То есть нам не нужны простейшие POST и DELETE операции по адресу /actors, мы хотим написать чуть более сложные обработчики для этого адреса вручную в контроллере.

Для этого можно просто отключить генерацию адресов для POST и DELETE в классе, который реализует RepositoryRestConfigurer переопределяя метод configureRepositoryRestConfiguration.

package com.SpringBootApps.firstSpringBootDataCRUDApp.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; import org.springframework.http.HttpMethod; @Configuration public class MyDataRestConfig implements RepositoryRestConfigurer { @Override public void configureRepositoryRestConfiguration( RepositoryRestConfiguration config, CorsRegistry cors) { //здесь в массив передаем не нужные для генерации HTTP методы HttpMethod[] theUnsupportedActions={HttpMethod.POST, HttpMethod.DELETE}; config.getExposureConfiguration() //Ниже пишем класс, который мы передавали //в JpaRepository на основе //которого генерировались методы для CRUD. .forDomainType(Actor.class) //Передаем в методы ниже созданный массив. .withItemExposure((metadata, httpMethods) -> httpMethods.disable(theUnsupportedActions)) .withCollectionExposure((metadata, httpMethods) -> httpMethods.disable(theUnsupportedActions)); //Все. Автоматическая генерация адресов для совершения //Post и Delete запросов над сущностью actor отключена. } }

Создание Spring Boot Data CRUD-приложения

Создадим CRUD приложение, только теперь с использованием Spring Boot Data.

Создадим новое Spring Boot приложение с такими папками и файлами.

Содержимое файлов в этом crud приложении ничем не отличается от содержимого предыдущего crud приложения, которое мы создавали в прошлом уроке за исключением файла класса сервиса.

Также можно заметить, что DAO интерфейс и класс исчезли, а вместо них появился файл ActorRepository. Давайте же разберемся почему.

До сего момента мы делали Crud операции, только над актером.

А что если в нашем приложении нам придется совершать CRUD операции не только с ним, а и с другими сущностями?

Получается придется для каждой сущности писать DAO интерфейс и его реализацию с почти одинаковыми CRUD методами.

Spring Data избавит нас от этой головной боли!

Благодаря Spring Data, дао классы вообще не пишутся. Вместо этого для каждой сущности пишется один единственный интерфейс, наследующий от другого интерфейса, он называется JpaRepository<[сюда передаем имя класса сущности], [сюда тип ID сущности]> (ниже пример), а методы для простых CRUD операций уже реализованы за нас и мы просто используем их в сервисе. То есть Spring Data фактически дарит нам методы для работы с БД и вручную, как раньше, их писать не нужно.

Также JpaRepository предоставляет нам и другие методы, здесь посмотрим только CRUD.

package com.SpringBootApps.firstSpringBootCRUDApp.dao; import java.util.List; //Вот интерфейс о котором мы говорили. //Для каждой другой сущности пишется подобный. //Класс таблицы actor это Actor, а тип id в этой таблице как мы //помним это Integer. Поэтому передаем эти данные в JpaRepository. public interface ActorRepository extends JpaRepository { //А здесь, если для сущности актер нужны только самые //обычные Crud операции, можно даже ничего не писать!!! //Но обычно здесь пишутся методы со специальным именем //чтобы делать особенные запросы к таблице с актерами. }

Теперь давайте же воспользуемся этими подаренными Spring Data методами для совершения crud операций в сервисном классе.

package com.SpringBootApps.firstSpringBootDataCRUDApp.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.SpringBootApps.firstSpringBootDataCRUDApp.models.Actor; import com.SpringBootApps.firstSpringBootDataCRUDApp.repositories.ActorRepository; import java.util.Optional; @Service public class ServiceInterfaceImpl implements ServiceInterface { //Внедряем созданный интерфейс (вернее бин, который будет //создан на основе этого интерфейса за кулисами средствами //Spring Boot). Где-то там за кулисами ясное дело спринг //берет созданный нами интерфейс ActorRepository, реализует //его (то есть реализует методы, которые наш интерфейс //наследовал от интерфейса JpaRepository) и внедряет //объект(бин) реализованного класса сюда. @Autowired private ActorRepository actorRepository; @Transactional public Actor getActor(int actorID) { //.findById() – дается нам БЕСПЛАТНО! //То есть нам не нужно создавать метод для взаимодействия //с БД вручную как мы это делали раньше. Optional actor = actorRepository.findById(actorID); //Немного об Optional. Здесь вам просто нужно знать что //в Optional объекте может оказаться либо пустота либо //извлеченный по Id актер. Не будем углубляться. Optional //это просто дополнительная страховка на случай того //если actorRepository.findById(actorID) вернет null. if(actor.isPresent()) { //Если не пустая возвращаем актера. return actor.get(); } return null;//иначе null } @Transactional public void saveActor(Actor actor) { //.save() – БЕСПЛАТНО! actorRepository.save(actor); } @Transactional public List getListOfActors() { //.findAll() – БЕСПЛАТНО! return actorRepository.findAll(); } @Transactional public void deleteActor(int actorid) { //.deleteById() – БЕСПЛАТНО! actorRepository.deleteById(actorid); } }

Давайте протестируем данное Crud приложение.

В таблице actor сейчас три актера:

Для примера давайте протестируем обработчик getActors, который выбирает всех актеров из таблицы.

Как видим, все актеры были выбраны обработчиком getActors из БД успешно. Это значит, что подаренный Spring Data метод findAll() работает.

Создание Spring Boot CRUD-приложения

Создадим Spring Boot Crud приложение. Это приложение будет предоставлять ту же функциональность, что и предыдущее CRUD приложение. То есть между клиентом и сервером будет происходить обмен JSON файлами с помощью REST.

Создадим новое Spring Boot приложение с такими папками и файлами.

Здесь содержимое всех файлов идентично предыдущему CRUD приложению, которое разбиралось в разделе “Spring MVC CRUD”, за исключением pom, содержимое которого идентично файлу pom предыдущего урока в этом разделе, DAO класса и файла настроек application.propreties.

В application.propreties находятся самые основные настройки, а именно данные для подключения к БД и настройка корневого пути.

server.servlet.context-path=/firstSpringBootCRUDApp spring.datasource.url=jdbc:mysql://localhost/storage spring.datasource.username=root spring.datasource.password=07998MSD spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

Помните в MVC приложении с использованием Hibernate мы в applicationContext.xml определяли бины DataSource и SessionFactory?

Spring Boot создает эти бины автоматически и нам не приходиться писать весь код этих бинов, где легко допустить ошибку. Нам просто нужно написать данные бд (database-url, login, password) в application.properties.

Также важно упомянуть, что теперь вместо бина SessionFactory создается и внедряется в ДАО класс бин EntityManager.

EntityManagerэто тот же самый SessionFactory, только как-бы обертка над ним (то есть добавляет доп функциональность SessionFactory).

Чтобы узнать какая это функциональность копнем чуть глубже.

Что такое JPA? Это стандарт для реализации фреймворков по типу Hibernate, то есть фреймворков для связывания таблиц БД и объектов. Да, существует не только Hibernate.

JPA это набор интерфейсов (под интерфейсом имеется ввиду ООП интерфейс, то есть тот интерфейс, который реализуют классы).

Фреймворки по типу Hibernate реализуют эти интерфейсы (то есть классы фреймворка Hibernate реализуют набор JPA интерфейсов).

И благодаря EntityManager можно легко переходить с одной реализации этих JPA интерфейсов на другую почти ничего не меняя в программе.

То есть без удобных средств Spring Boot нам пришлось бы вручную переконфигурировать весь проект, чтобы, например, перейти с Hibernate на, например, стандартную реализацию JPA

Взглянем как через EntityManger можно преходить между реализациями ничего не меняя в проекте.

Ниже в одном и том же DAO классе используется и Hibernate и стандартная JPA реализация.

И это благодаря EntityManager, в обычном не Spring Boot приложении такое не было бы возможно. Ну или правильнее сказать, такое очень сложно было бы настроить вручную.

package com.SpringbootApps.firstSpringBootCRUDApp.Dao; import java.util.List; import org.springframework.stereotype.Repository; @Repository public class ActorDAOImpl implements ActorDAO { // Внедряем EntityManager @Autowired private EntityManager entityManager; public Actor getActor(int actorId) { // Извлекаем новую Hibernate session также // как это делали через sessionFactory // в прошлых Crud приложениях, // только в данном случае // из entityManager и другим методом. Session session = entityManager.unwrap(Session.class); return session.get(Actor.class, actorId); } public List getListOfActors() { // А здесь давайте воспользуемся стандартной // реализацией JPA. То есть это не Hibernate. // Как видим сессии нет. Со стандартной реализацией // работаем прямо через entityManager. return entityManager.createQuery(“from Actor”).getResultList(); } public void saveActor(Actor actor) { Session session = entityManager.unwrap(Session.class); session.saveOrUpdate(actor); } public void deleteActor(int actorId) { Session session = entityManager.unwrap(Session.class); session.createQuery(“delete from Actor ” + “where id=:actorid”).setParameter( “actorid”, actorId).executeUpdate(); } }

В таблице actor сейчас три актера:

Давайте получим их данные всех актеров в формате JSON через обработчик getActors. Этот обработчик извлекает актеров из БД помещает их в List и отправляет этот List с актерами в формате JSON. Отметим, что актеры, как мы только что видели в классе, извлекаются с помощью стандартной реализации JPA.

Обратимся к этому обработчику через Rest Client по ссылке /actors запросом GET. RestController распознает, что был сделан GET запрос и вызывает обработчик помеченный аннотацией GetMapping.

Как видим, List с объектами актеров из БД успешно пришел в формате JSON в ответ на GET запрос.

То есть мы успешно воспользовались стандартными реализациями JPA.

Теперь давайте протестируем какой-нибудь метод, который использует Hibernate реализации, чтобы убедиться, что разные реализации JPA могут быть использованы в одном проекте.

Давайте извлечем третьего актера с помощью обработчика getActor.

Как видим, всё прошло успешно. То есть обе реализации JPA могут одновременно использоваться в одном проекте благодаря EntityManager и конечно же Spring Boot.

Spring Boot: в чём его сила и удобство?

Что такое Spring Boot приложение?

Spring Boot приложение это почти то же самое Spring приложение, что мы изучали раньше, но с дополнительными средствами упрощающими разработку. Эти средства помогают легче конфигурировать наше приложение и взаимодействовать с ним.

Отметим основные моменты:

1. Во первых, в Spring Boot приложении сокращено количество файлов (нет файла настроек бинов applicationContext, файла web.xml и других), которые нужно было конфигурировать вручную и где легко ошибиться и потом долго искать ошибку.

В Spring Boot приложении нам вообще не придется лезть в папку WEB-INF (которой собственно и нет в Spring Boot приложении). Спринг Бут сам за кулисами конфигурирует приложение. А нужные нам настройки пишутся в отдельном файле настроек application.properties в очень коротком и простом виде.

2. Второй важный момент, что не нужно беспокоиться о совместимости версий зависимостей в pom, ведь часто приходиться тратить много времени на поиск совместимых. Спринг Бут предоставляет специальные зависимости, которые представляют собой наборы всем нам известных ранее зависимостей, которые уже сразу между собой совместимы.

3. В третьих, стартовый Спринг Бут проект можно настроить (настроить стартовые зависимости, тип сборщика Maven или Gradle и т.д.) удобным интуитивным интерфейсом в интернете на сайте Spring Initializer и скачать этот настроенный проект с этого сайта. Что упрощает стартовую конфигурацию проекта.

Раньше, чтобы создать новый Spring проект, приходилось копировать старый проект и редактировать его, потому что создавать Spring проект с нуля морока серьезная. Спринг Бут же серьезно упрощает создание нового Спринг проекта.

Давайте зайдем на сайт https://start.spring.io/ , сконфигурируем и скачаем наше первое Spring Boot приложение:

Видим интерфейс для настройки стартового Spring Boot приложения. Здесь, как видим, можно выбрать сборщик проекта (они бывают разные, но мы выберем Maven), язык Spring Boot проекта, его версию, и также можно сразу настроить метаданные проекта (это то что, как мы помним, сверху в pom файле).

Также можно сразу добавить зависимости в проект. Для этого нажимаем на Add Dependencies…

В результате откроется окно в котором можно найти и добавить в проект набор зависимостей(как уже упоминалось, в сприг бут наборы, а не отдельные зависимости):

Добавим такие наборы зависимостей: Web(добавляет поддержку Spring MVC, сервлетов и т.д.), jpa(поддержка Hibernate и др.) и MySql Driver(для возможности подключения к базе).

Всё мы сконфигурировали наш стартовый проект.

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

Нажимаем Download. Наше первое Spring Boot приложение должно скачаться на компьютер.

4. Ну и наверное самый важный момент из выше перечисленных, это то что скачиваемый Spring Boot проект уже сразу в себе содержит Tomcat сервер на котором мы запускаем скачанное Spring Boot приложение. То есть нам не нужно скачивать Tomcat отдельно и запускать через него спринг приложения, сервер уже СРАЗУ встроен в приложение, которое скачано с сайта Spring Initializer.

Также Spring Boot имеет различную другую функциональность, которая будет рассмотрена далее.


Spring Boot приложение.

Давайте добавим скачанное стартовое приложение в Eclipse. Для этого переходим по пути File -> Open Projects from File System и там указываем путь к папке скачанного проекта. Взглянем на структуру и файлы Spring Boot приложения.

Как видим, оно отличается от тех Спринг приложений, которые были раньше. Видим, что папка WEB-INF в src отсутствует и присутствуют некоторые новые папки и файлы.

mvnwдля запуска мавен проекта из командной строки. Автоматически подгружает мавен при запуске проекта если он не установлен или обновляет его.

То есть запустить приложение можно из консоли не открывая IDE. Давайте сделаем это.

Вводим в папке приложения mvnw package, что загружает мавен если его нет и создает jar файл:

В папке target должен появиться jar файлик нашего приложения. Его теперь можно запустить.

Далее вводим java -jar (имя приложения).jar и оно запускается

properties – здесь все настройки Spring Boot приложения. Настройки совершаются в формате свойств типа some.prop=”value” (то есть имя свойства и его значение). В примере ниже можно увидеть, что таким образом можно настраивать приложение в очень лаконичной форме. Можно с помощью свойства server.port изменить порт на котором запускается наше приложение, а например с помощью server.servlet.context-path можно изменить корневой элемент в адресе. Также можно увидеть, что здесь настраиваются уровни логирования, данные для подключения к БД, время жизни сессии и вообще все основные настройки Спринг Бут приложения происходят здесь в этом файле.

Можно также добавлять сюда кастомные свойства типа some.custom.prop="value" и можно внедрить их в строку.

Кто помнит из прошлых уроков их можно извлекать в классах приложения с помощью аннотации @Value("\${some.custom.prop}") и внедрять в String поле.

#Lets watch on some of important settings. There are lots of them #we can change port of app. Now it will be 7070 instead of 8080 server.port=7070 #we can change context path – root element of addresses in project server.servlet.context-path=/firstSpringBootApp #can set lifetime of session server.servlet.session.timeout=120s #can set logging levels to folders of project logging.level.com.springboot=TRACE logging.level.org.hibernate=DEBUG #database info. spring.datasource.url=jdbc:mysql://localhost/storage spring.datasource.username=root spring.datasource.password=07998MSD spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

В папке static проекта хранятся все статические ресурсы, типа обычных html страниц,css файлов,картинок и т.д

В template хранятся динамические страницы типа JSP,Thyseleaf(аналог JSP)

pom файлик в Spring Boot приложении отличается некоторыми элементами от обычного веб приложения.

Здесь можно увидеть настройки, которые мы делали через интерфейс сайта Spring Initializr. То есть метаданные, версию спринг бута и также зависимости, которые мы включали в проект. Также здесь мы включим еще одну нужную зависимость devtools вручную, о который написано ниже.

4.0.0 org.springframework.boot spring-boot-starter-parent 3.1.5 com.SpringBootApps firstSpringBootApp 0.0.1-SNAPSHOT firstSpringBootApp Demo project for Spring Boot 17 org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web com.mysql mysql-connector-j runtime org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-devtools test org.springframework.boot spring-boot-maven-plugin

Аннотация @SpringBootApplication в файле ниже состоит из трех аннотаций@EnableAutoConfiguration (нужна для включения автоматической конфигурации Spring Boot) и знакомых нам @ComponentScan и @Configuration (кто помнит использовались в Java версии файла applicationContext.xml для конфигурации бинов).

То есть через этот класс происходит конфигурация приложения, только она происходит за кулисами средствами Spring Boot, а нам ничего конфигурировать не нужно.

Создание бинов на основе классов, помеченных специальными аннотациями (@Component, @Controller, @Repository, @Service), происходит с помощью аннотации @ComponentScan.

Эта аннотация автоматически сканирует все подпапки той директории, в которой находится класс с методом main() (обычно это Application.java).

Вернитесь немного назад и посмотрите на структуру проекта ещё раз: файл с main находится на уровень выше папки controller, в которой лежит контроллер.

То же самое должно быть и с остальными директориями — service, dao и другими.
Все они должны быть вложены в ту же корневую папку или быть её поддиректориями, чтобы @ComponentScan смог обнаружить и зарегистрировать все нужные бины, и приложение корректно работало.

Запуск приложения, автоматическая конфигурация бинов, сканирование поддеректорий и другие настройки происходят методом run в main методе.

Запускать Spring Boot приложение можно как обычное Java приложение, то есть через метод main, что удобно (можно воспользоваться кнопкой Run в Eclipse IDE).

package com.SpringBootApps.firstSpringBootApp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class FirstSpringBootAppApplication { public static void main(String[] args) { SpringApplication.run(FirstSpringBootAppApplication.class, args); } }

Давайте в классе контроллере создадим простенький обработчик для теста первого Спринг Бут приложения.

package com.SpringBootApps.firstSpringBootApp.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; public class MainController { @GetMapping(“/helloworld”) public String helloWorld() { return “Hello world!!!”; } }

Запустим приложение через main файл и перейдем к обработчику по определенному адресу /helloworld.

Как видим, обработчик успешно прислал строку в ответ на запрос.

При этом можно увидеть, что обращение к обработчику идет через порт 7070 и через корневой путь firstSpringBootApp. Это было настроено, как мы помним, в файле application.properties