Статьи

Создание REST API с помощью Spring Boot и MongoDB

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

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

Эта запись блога демонстрирует, как легко реализовать API REST, который обеспечивает операции CRUD для записей todo, которые сохраняются в базе данных MongoDB .

Давайте начнем с создания нашего проекта Maven.

Примечание. В этом сообщении предполагается, что вы уже установили базу данных MongoDB. Если вы этого не сделали, вы можете следовать инструкциям, приведенным в сообщении в блоге: Доступ к данным с помощью MongoDB .

Создание нашего проекта Maven

Мы можем создать наш проект Maven, выполнив следующие действия:

  1. Используйте POM spring-boot-starter-parent в качестве родительского POM нашего проекта Maven. Это гарантирует, что наш проект наследует разумные настройки по умолчанию от Spring Boot.
  2. Добавьте плагин Spring Boot Maven в наш проект. Этот плагин позволяет нам упаковать наше приложение в исполняемый файл JAR, упаковать его в военный архив и запустить приложение.
  3. Настройте зависимости нашего проекта. Нам нужно настроить следующие зависимости:
    • Зависимость spring-boot-starter-web предоставляет зависимости веб-приложения.
    • Зависимость spring-data-mongodb обеспечивает интеграцию с базой данных документов MongoDB.
  4. Включить поддержку Java 8 Spring Boot.
  5. Настройте основной класс нашего приложения. Этот класс отвечает за настройку и запуск нашего приложения.

Соответствующая часть нашего файла pom.xml выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<properties>
    <!-- Enable Java 8 -->
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- Configure the main class of our Spring Boot application -->
    <start-class>com.javaadvent.bootrest.TodoAppConfig</start-class>
</properties>
         
<!-- Inherit defaults from Spring Boot -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.9.RELEASE</version>
</parent>
 
<dependencies>
    <!-- Get the dependencies of a web application -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
 
    <!-- Spring Data MongoDB-->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-mongodb</artifactId>
    </dependency>
</dependencies>
 
<build>
    <plugins>
        <!-- Spring Boot Maven Support -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Дополнительное чтение:

Давайте продолжим и узнаем, как мы можем настроить наше приложение.

Настройка нашего приложения

Мы можем настроить наше приложение Spring Boot, выполнив следующие действия:

  1. Создайте класс TodoAppConfig для пакета com.javaadvent.bootrest .
  2. Включить автозагрузку Spring Boot.
  3. Сконфигурируйте контейнер Spring для сканирования компонентов, найденных в дочерних пакетах пакета com.javaadvent.bootrest .
  4. Добавьте метод main () в класс TodoAppConfig и реализуйте его, запустив наше приложение.

Исходный код класса TodoAppConfig выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package com.javaadvent.bootrest;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
 
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class TodoAppConfig {
     
    public static void main(String[] args) {
        SpringApplication.run(TodoAppConfig.class, args);
    }
}

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

Дополнительное чтение:

Давайте перейдем к реализации нашего REST API.

Реализация нашего REST API

Нам нужно реализовать REST API, который обеспечивает операции CRUD для записей todo. Требования нашего REST API:

  • Запрос POST, отправляемый на URL ‘/ api / todo’, должен создать новую запись todo, используя информацию, найденную в теле запроса, и вернуть информацию о созданной записи todo.
  • Запрос DELETE, отправленный на URL ‘/ api / todo / {id}’, должен удалить запись todo, идентификатор которой найден в URL, и вернуть информацию об удаленной записи todo.
  • Запрос GET, отправленный на URL ‘/ api / todo’, должен вернуть все записи todo, найденные в базе данных.
  • Запрос GET, отправляемый на URL ‘/ api / todo / {id}’, должен возвращать информацию о записи todo, идентификатор которой найден из URL.
  • Запрос PUT, отправляемый на URL ‘/ api / todo / {id}’, должен обновить информацию о существующей записи todo, используя информацию, найденную в теле запроса, и вернуть информацию об обновленной записи todo.

Мы можем выполнить эти требования, выполнив следующие действия:

  1. Создайте сущность, которая содержит информацию об одной записи todo.
  2. Создайте репозиторий, который используется для сохранения записей задач в базе данных MongoDB, и найдите в них записи задач.
  3. Создайте сервисный уровень, который отвечает за отображение DTO в доменные объекты и наоборот. Цель нашего сервисного уровня — изолировать нашу модель домена от веб-слоя.
  4. Создайте класс контроллера, который обрабатывает HTTP-запросы и возвращает правильный ответ клиенту.

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

Давайте начнем.

Создание сущности

Нам нужно создать класс сущностей, который содержит информацию об одной записи todo. Мы можем сделать это, выполнив следующие действия:

  1. Добавьте поля id , description и title в созданный класс сущностей. Сконфигурируйте поле id объекта, добавив в поле id аннотацию @Id .
  2. Укажите константы ( MAX_LENGTH_DESCRIPTION и MAX_LENGTH_TITLE ), которые определяют максимальную длину полей описания и заголовка .
  3. Добавьте класс статического построителя в класс сущности. Этот класс используется для создания новых объектов Todo .
  4. Добавьте метод update () в класс сущностей. Этот метод просто обновляет заголовок и описание объекта, если в качестве параметров метода указаны действительные значения.

Исходный код класса Todo выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import org.springframework.data.annotation.Id;
 
import static com.javaadvent.bootrest.util.PreCondition.isTrue;
import static com.javaadvent.bootrest.util.PreCondition.notEmpty;
import static com.javaadvent.bootrest.util.PreCondition.notNull;
 
final class Todo {
 
    static final int MAX_LENGTH_DESCRIPTION = 500;
    static final int MAX_LENGTH_TITLE = 100;
 
    @Id
    private String id;
 
    private String description;
 
    private String title;
 
    public Todo() {}
 
    private Todo(Builder builder) {
        this.description = builder.description;
        this.title = builder.title;
    }
 
    static Builder getBuilder() {
        return new Builder();
    }
 
    //Other getters are omitted
 
    public void update(String title, String description) {
        checkTitleAndDescription(title, description);
 
        this.title = title;
        this.description = description;
    }
 
    /**
     * We don't have to use the builder pattern here because the constructed
  * class has only two String fields. However, I use the builder pattern
  * in this example because it makes the code a bit easier to read.
     */
    static class Builder {
 
        private String description;
 
        private String title;
 
        private Builder() {}
 
        Builder description(String description) {
            this.description = description;
            return this;
        }
 
        Builder title(String title) {
            this.title = title;
            return this;
        }
 
        Todo build() {
            Todo build = new Todo(this);
 
            build.checkTitleAndDescription(build.getTitle(), build.getDescription());
 
            return build;
        }
    }
 
    private void checkTitleAndDescription(String title, String description) {
        notNull(title, "Title cannot be null");
        notEmpty(title, "Title cannot be empty");
        isTrue(title.length() <= MAX_LENGTH_TITLE,
                "Title cannot be longer than %d characters",
                MAX_LENGTH_TITLE
        );
 
        if (description != null) {
            isTrue(description.length() <= MAX_LENGTH_DESCRIPTION,
                    "Description cannot be longer than %d characters",
                    MAX_LENGTH_DESCRIPTION
            );
        }
    }
}

Дополнительное чтение:

Давайте продолжим и создадим репозиторий, который связывается с базой данных MongoDB.

Создание хранилища

Мы должны создать интерфейс репозитория, который используется для сохранения объектов Todo в базе данных MondoDB и извлечения объектов Todo из нее.

Если мы не хотим использовать поддержку Spring 8 для Spring Data, мы могли бы создать наш репозиторий, создав интерфейс, расширяющий интерфейс CrudeRepository <T, ID> . Однако, поскольку мы хотим использовать поддержку Java 8, мы должны выполнить следующие шаги:

  1. Создайте интерфейс, расширяющий интерфейс хранилища <T, ID> .
  2. Добавьте следующие методы хранилища в созданный интерфейс:
    1. Метод void delete (удаленный Todo) удаляет запись todo, заданную в качестве параметра метода.
    2. Метод List <Todo> findAll () возвращает все записи задач, найденные в базе данных.
    3. Необязательный метод <Todo> findOne (String id) возвращает информацию об одной записи todo. Если запись todo не найдена, этот метод возвращает пустое значение Optional .
    4. Метод сохранения Todo (сохранение Todo) сохраняет новую запись задачи в базу данных и возвращает сохраненную запись задачи.

Исходный код интерфейса TodoRepository выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import org.springframework.data.repository.Repository;
 
import java.util.List;
import java.util.Optional;
 
interface TodoRepository extends Repository<Todo, String> {
 
    void delete(Todo deleted);
 
    List<Todo> findAll();
 
    Optional<Todo> findOne(String id);
 
    Todo save(Todo saved);
}

Дополнительное чтение:

Давайте продолжим и создадим сервисный уровень нашего примера приложения.

Создание сервисного уровня

Во-первых , мы должны создать сервисный интерфейс, который обеспечивает операции CRUD для записей todo. Исходный код интерфейса TodoService выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
import java.util.List;
 
interface TodoService {
 
    TodoDTO create(TodoDTO todo);
 
    TodoDTO delete(String id);
 
    List<TodoDTO> findAll();
 
    TodoDTO findById(String id);
 
    TodoDTO update(TodoDTO todo);
}

Класс TodoDTO — это DTO, который содержит информацию об одной записи todo. Мы поговорим об этом больше, когда создадим веб-слой нашего примера приложения.

Во-вторых , мы должны реализовать интерфейс TodoService. Мы можем сделать это, выполнив следующие действия:

  1. Внедрите наш репозиторий в класс обслуживания, используя внедрение конструктора.
  2. Добавьте частный метод Todo findTodoById (String id) в класс обслуживания и реализуйте его, возвращая найденный объект Todo или выбрасывая исключение TodoNotFoundException .
  3. Добавьте частный метод TodoDTO convertToDTO (модель Todo) в класс обслуживания и реализуйте его, преобразовав объект Todo в объект TodoDTO и вернув созданный объект.
  4. Добавьте закрытый List <TodoDTO> convertToDTOs (List <Todo> models) и реализуйте его, преобразовав список объектов Todo в список объектов TodoDTO и вернув созданный список.
  5. Реализуйте метод создания TodoDTO (TodoDTO todo) . Этот метод создает новый объект Todo , сохраняет созданный объект в базе данных MongoDB и возвращает информацию о созданной записи todo.
  6. Реализуйте метод удаления (String id) TodoDTO . Этот метод находит удаленный объект Todo , удаляет его и возвращает информацию об удаленной записи todo. Если объект Todo с данным идентификатором не найден, этот метод генерирует исключение TodoNotFoundException .
  7. Реализуйте метод List <TodoDTO> findAll () . Этот метод извлекает все объекты Todo из базы данных, преобразует их в список объектов TodoDTO и возвращает созданный список.
  8. Реализуйте метод findById (String id) TodoDTO . Этот метод находит объект Todo из базы данных, преобразует его в объект TodoDTO и возвращает созданный объект TodoDTO . Если запись todo не найдена, этот метод генерирует исключение TodoNotFoundException .
  9. Реализуйте метод обновления TodoDTO (TodoDTO todo) . Этот метод находит обновленный объект Todo из базы данных, обновляет его заголовок и описание , сохраняет его и возвращает обновленную информацию. Если обновленный объект Todo не найден, этот метод генерирует исключение TodoNotFoundException .

Исходный код MongoDBTodoService выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.util.List;
import java.util.Optional;
 
import static java.util.stream.Collectors.toList;
 
@Service
final class MongoDBTodoService implements TodoService {
 
    private final TodoRepository repository;
 
    @Autowired
    MongoDBTodoService(TodoRepository repository) {
        this.repository = repository;
    }
 
    @Override
    public TodoDTO create(TodoDTO todo) {
        Todo persisted = Todo.getBuilder()
                .title(todo.getTitle())
                .description(todo.getDescription())
                .build();
        persisted = repository.save(persisted);
        return convertToDTO(persisted);
    }
 
    @Override
    public TodoDTO delete(String id) {
        Todo deleted = findTodoById(id);
        repository.delete(deleted);
        return convertToDTO(deleted);
    }
 
    @Override
    public List findAll() {
        List todoEntries = repository.findAll();
        return convertToDTOs(todoEntries);
    }
 
    private List convertToDTOs(List models) {
        return models.stream()
                .map(this::convertToDTO)
                .collect(toList());
    }
 
    @Override
    public TodoDTO findById(String id) {
        Todo found = findTodoById(id);
        return convertToDTO(found);
    }
 
    @Override
    public TodoDTO update(TodoDTO todo) {
        Todo updated = findTodoById(todo.getId());
        updated.update(todo.getTitle(), todo.getDescription());
        updated = repository.save(updated);
        return convertToDTO(updated);
    }
 
    private Todo findTodoById(String id) {
        Optional result = repository.findOne(id);
        return result.orElseThrow(() -> new TodoNotFoundException(id));
 
    }
 
    private TodoDTO convertToDTO(Todo model) {
        TodoDTO dto = new TodoDTO();
 
        dto.setId(model.getId());
        dto.setTitle(model.getTitle());
        dto.setDescription(model.getDescription());
 
        return dto;
    }
}

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

Создание класса контроллера

Во-первых , нам нужно создать класс DTO, который содержит информацию об одной записи todo и определяет правила проверки, которые используются, чтобы гарантировать, что в базу данных может быть сохранена только действительная информация. Исходный код класса TodoDTO выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
import org.hibernate.validator.constraints.NotEmpty;
 
import javax.validation.constraints.Size;
 
public final class TodoDTO {
 
    private String id;
 
    @Size(max = Todo.MAX_LENGTH_DESCRIPTION)
    private String description;
 
    @NotEmpty
    @Size(max = Todo.MAX_LENGTH_TITLE)
    private String title;
 
    //Constructor, getters, and setters are omitted
}

Дополнительное чтение:

Во-вторых , нам нужно создать класс контроллера, который обрабатывает запросы HTTP, отправленные нашему REST API, и отправляет правильный ответ клиенту. Мы можем сделать это, выполнив следующие действия:

  1. Внедрите наш сервис в наш контроллер с помощью инжектора конструктора.
  2. Добавьте метод create () в наш контроллер и реализуйте его, выполнив следующие действия:
    1. Прочитайте информацию о созданной записи todo из тела запроса.
    2. Проверьте информацию о созданной записи todo.
    3. Создайте новую запись todo и верните созданную запись todo. Установите статус ответа на 201.
  3. Внедрите метод delete () , делегировав идентификатор удаленной записи задачи в нашу службу и вернув удаленную запись задачи.
  4. Реализуйте метод findAll () , найдя записи todo из базы данных и вернув найденные записи todo.
  5. Реализуйте метод findById () , найдя запись todo из базы данных и вернув найденную запись todo.
  6. Реализуйте метод update () , выполнив следующие действия:
    1. Прочитайте информацию об обновленной записи todo из тела запроса.
    2. Проверьте информацию об обновленной записи todo.
    3. Обновите информацию о записи todo и верните обновленную запись todo.
  7. Создайте метод @ExceptionHandler, который устанавливает статус ответа 404, если запись todo не найдена ( исключение TodoNotFoundException ).

Исходный код класса TodoController выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
 
import javax.validation.Valid;
import java.util.List;
 
@RestController
@RequestMapping("/api/todo")
final class TodoController {
 
    private final TodoService service;
 
    @Autowired
    TodoController(TodoService service) {
        this.service = service;
    }
 
    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    TodoDTO create(@RequestBody @Valid TodoDTO todoEntry) {
        return service.create(todoEntry);
    }
 
    @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
    TodoDTO delete(@PathVariable("id") String id) {
        return service.delete(id);
    }
 
    @RequestMapping(method = RequestMethod.GET)
    List<TodoDTO> findAll() {
        return service.findAll();
    }
 
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    TodoDTO findById(@PathVariable("id") String id) {
        return service.findById(id);
    }
 
    @RequestMapping(value = "{id}", method = RequestMethod.PUT)
    TodoDTO update(@RequestBody @Valid TodoDTO todoEntry) {
        return service.update(todoEntry);
    }
 
    @ExceptionHandler
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public void handleTodoNotFound(TodoNotFoundException ex) {
    }
}

Примечание. Если проверка не удалась, наш REST API возвращает ошибки проверки в формате JSON и устанавливает статус ответа 400. Если вы хотите узнать больше об этом, прочитайте сообщение в блоге под названием: Spring from the Trenches: Добавление проверки в REST API ,

Вот и все. Теперь мы создали REST API, который обеспечивает операции CRUD для записей todo и сохраняет их в базе данных MongoDB. Давайте подведем итог тому, что мы узнали из этого поста в блоге.

Резюме

Этот пост научил нас трем вещам:

  • Мы можем получить необходимые зависимости с помощью Maven, объявив только две зависимости: spring-boot-starter-web и spring-data-mongodb .
  • Если нас устраивает конфигурация Spring Boot по умолчанию, мы можем настроить наше веб-приложение, используя поддержку автоматической настройки и «сбрасывая» новые файлы jar в classpath.
  • Мы научились создавать простой REST API, который сохраняет информацию в базе данных MongoDB и находит информацию из нее.

Вы можете получить пример приложения к этому сообщению в блоге от Github .

Ссылка: Создание REST API с помощью Spring Boot и MongoDB от нашего партнера по JCG Аттилы Михали Балаза в блоге Java Advent Calendar .