Spring Boot REST API

В этой статье мы напишем маленькое приложение на Spring Boot, которое предоставляет REST-сервисы.
Архитектура приложения будет стандартна и включать несколько слоев: dao, (service отсутствует) и controller. Все шаги по построению приложения просты. Код приложения можно скачать на GitHub.

Spring Initializr

Заготовку любого проекта на Spring Boot удобно взять на https://start.spring.io/. Здесь мы придумываем имя группы и имя артифакта будущего проекта на Maven, выбираем dependency, которые нам точно понадобятся и генерируем проект. А потом импортируем его в Eclipse как Maven-проект.

Инициализация Spring Boot проекта
Инициализация Spring Boot проекта

Нам понадобятся зависимости WEB, JPA и H2.

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

Maven-зависимости

В результате получаем сгенерированный POM с такими зависимостями:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
</dependencies>

Хоть файл и сгенерирован, это не мешает нам добавлять в него новые зависимости при необходимости.

Слои (multi-layer architecture)

Импортированный проект выглядит так, плюс мы создали пакеты для моделей, dao и контроллеров (выделены красным):

Service-layer отсутствует потому, что приложение слишком простое, бизнес-логики тут нет.

Модель

Модель будет состоять из одного класса Person:

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)

    private Long id;
    @NotNull
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }
    // ...getters and setters
}

Person аннотирован как JPA-сущность, то есть при запуске приложения в базе данных  будет создана таблица с таким именем и полями.

DAO

DAO-layer предназначен для работы с данными. У нас он состоит из одного бина PersonRepository.

Благодаря аннотации @Repository и интерфейсу JpaRepository DAO-layer предельно прост:

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {

}

Мы создаем бин PersonRepository, аннотируя его с помощью @Repository. Полученный бин реализует все методы интерфейса, можно ничего не писать самостоятельно, если не нужны какие-то особые запросы к базе. А стандартные операции поиска, добавления и удаления тут все реализованы.

Service-layer опускаем, поскольку приложение простое. В контроллере будем использовать бин PersonRepository.

Контроллер

Здесь реализованы запросы поиска, добавления, редактирования и удаления Person.

  • Класс аннотирован @RestController и указан основной путь к запросам  этого контроллера- “/persons”.
  • С помощью аннотации @Autowired бин personRepository инжектирован в поле контроллера – теперь его можно использовать.
@RestController
@RequestMapping("/persons")
public class PersonController {

    @Autowired
    private PersonRepository personRepository;

    @GetMapping
    public ResponseEntity<List<Person>> listAllPersons() {
        personRepository.save(new Person("Kate"));
        List<Person> persons = personRepository.findAll();
        return ResponseEntity.ok().body(persons);

    }

    @GetMapping(value = "/{personId}")
    public ResponseEntity<Person> getPerson(@PathVariable("personId") Long personId)
      throws EntityNotFoundException {

        Optional<Person> person = personRepository.findById(personId);
        if (!person.isPresent())
            throw new EntityNotFoundException("id-" + personId);
        return ResponseEntity.ok().body(person.get());

    }

    @PostMapping
    public ResponseEntity<Person> createPerson(@RequestBody @Valid Person person) {
        Person p = personRepository.save(person);
        return ResponseEntity.status(201).body(p);
    }

    @PutMapping(value = "/{personId}")
    public ResponseEntity<Person> updatePerson(@RequestBody @Valid Person person,
            @PathVariable("personId") Long personId) throws EntityNotFoundException {
        Optional<Person> p = personRepository.findById(personId);
        if (!p.isPresent())
            throw new EntityNotFoundException("id-" + personId);
        return ResponseEntity.ok().body(personRepository.save(person));
    }

    @DeleteMapping(value = "/{personId}")
    public ResponseEntity<Person> deletePerson(@PathVariable("personId") Long personId) 
        throws EntityNotFoundException {
        Optional<Person> p = personRepository.findById(personId);
        if (!p.isPresent())
            throw new EntityNotFoundException("id-" + personId);

        personRepository.deleteById(personId);
        return ResponseEntity.ok().body(p.get());
    }
}

Тут два метода для получения данных (аннотации @GetMapping) и три – для редактирования. Все методы аннотированны:

  • @GetMapping – для GET-запросов, получения Person
  • @PostMapping – для POST-запросов, т.е. добавления Person
  • @PutMapping – для PUT-запросов, редактирования Person
  • @DeleteMapping – для DELETE-запросов, удаления Person

Возвращаем обычно ResponseEntity<Person>, это более гибкий вариант, чем вернуть просто Person, поскольку для ResponseEntity можно установить Http-статус ответа – ResponseEntity.ok() – это 200 или ResponseEntity.status(201).

В методе body() передается возвращаемая сущность – в вышеприведенных методах это Person (либо список Person). Под капотом она конвертируется в JSON благодаря тому, что у нас стоит аннотация @RestController. Для конвертации под капотом Spring Boot использует библиотеку Jackson – она включена благодаря Maven-зависимости spring-boot-starter-web.

Если надо возвратить JSON с описанием ошибки, выбрасываем исключение. Например, если запрос на редактирование содержит id несуществующего Person, то выбрасываем EntityNotFoundException. Как обрабатывать исключения и кастомизировать JSON с ошибкой, описано в следующей статье.

Запуск

Для запуска Spring Boot приложения запускаем main() этого класса:

@SpringBootApplication
public class SpringBootRestApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRestApplication.class, args);
    }
}

При этом будет запущен веб-сервер, отдельно его устанавливать и запускать не надо – это одно из преимуществ Spring Boot приложения.

Также не надо задавать пути для поиска бинов, они найдутся автоматически. Единственное, класс SpringBootRestApplication не надо перекладывать в подпакет, он должен быть на верхнем уровне иерархии, иначе с поиском бинов возникнут проблемы. Когда мы сгенерировали заготовку приложения, этот файл уже был именно там, где надо – перекладывать его не следует.

Тестирование

Осталось проверить, что методы контроллера работают. Составлять запросы будем с помощью бесплатного графического приложения Postman (но учтите, оно ресурсоемкое). Как писать тесты, рассмотрим в другой статье.

Добавление Person
POST http://localhost:8080/persons/
{
  "name": "Joe"
}

Ответ, возвращается вновь добавленный Person с id=1:

{
    "id": 1,
    "name": "Joe"
}
Редактирование Person
PUT http://localhost:8080/persons/1
{
  "name": "Jane"
}

Ответ, возвращается отредактированный Person с id=1 и name=’Jane’:

{
    "id": 1,
    "name": "Jane"
}
Получение Person
GET http://localhost:8080/persons/1

Ответ, возвращается Person:

{
    "id": 1,
    "name": "Jane"
}
Получение списка Person
GET http://localhost:8080/persons/

Ответ, возвращается список, состоящий из одного элемента Person:

[
    {
        "id": 1,
        "name": "Jane"
    }
]
Удаление Person
DELETE http://localhost:8080/persons/1

Ответ, возвращается удаленный Person с id=1 и name=’Jane’:

{
    "id": 1,
    "name": "Jane"
}

Заключение

Мы написали маленькое приложение, предоставляющее REST-сервис. Как обрабатывать исключения описано в следующей части.

Spring Boot REST API: 2 комментария

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *