Статьи

Spring Data JPA с центральной обработкой исключений и валидацией VO — каркас

1. Введение

Со временем среда Spring стала стандартом де-факто для создания любых приложений на основе REST API. Spring предлагает множество готовых компонентов, чтобы избежать написания повторяющегося и громоздкого кода котельной плиты. Кроме того, прекрасная вещь о весне в том, что если есть готовое решение; он предлагает вам легкие способы интеграции с этой доступной библиотекой / структурой. В этой статье мы рассмотрим, как написать RESTful API на основе Spring с использованием технологии полного стека Spring; Spring Boot, Spring Validations и Spring data JPA с примером, который показывает полную информацию о следующем:

  • Spring Boot и его конфигурация
  • Управление зависимостями с помощью загрузчиков Spring
  • Избегать узкого места в DAO-коде с использованием Spring-данных JPA.
  • Поддержка Spring для валидации на уровне VO.
  • Централизованная обработка исключений.

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

2. Создайте проект

Вот шаги, которые необходимо было выполнить для создания проекта.

2.1 Spring Intializer

Spring предоставляет удобный инструмент для создания проектов в этом месте Spring INITIALIZR . На этой веб-странице вы можете загрузить свое приложение, добавив необходимые зависимости. Вы можете создать скелет проекта, добавив 3 зависимости, упомянутые ниже (см. Изображение ниже для ясного понимания).
1. «Веб»: эта зависимость необходима для кодирования веб-слоя и создания API. Когда проект генерируется, он отображается в виде следующей зависимости в файле build.gralde.
compile('org.springframework.boot:spring-boot-starter-web')
2. «Проверка»: чтобы включить проверку пружины. Это проявляется как следующая зависимость в build.gradle. compile('org.springframework.boot:spring-boot-starter-validation')
3. «JPA»: включить использование данных пружины JPA. Это проявляется как следующая зависимость в build.gradle.
compile('org.springframework.boot:spring-boot-starter-data-jpa')

Spring data jpa - Spring Initializer

Spring Initializer

2.2 Конфигурация Eclipse

Создайте проект и импортируйте его в затмение. Как только это будет сделано, все готово для создания вашего API. Проект, импортированный в затмение, должен выглядеть следующим образом.

Spring data jpa - Структура проекта

Структура проекта

3. Создайте API

Прежде чем писать API, давайте создадим пакеты в соответствии с соглашениями Java, как показано ниже.

Spring data jpa - пакеты java

Java-пакеты

Сгенерированный код, мы получаем класс в корневом пакете, то есть com.example.spring.springrestapp. Это наш класс загрузки.

Spring Data JPA - класс загрузки

Учебный класс

Примечание. Классы запуска должны создаваться на уровне корневого пакета с настройками по умолчанию.
Теперь давайте продолжим и создадим класс для контроллера и метод API для добавления пользовательских данных в БД. Для этого API, который мы собираемся построить, давайте примем некоторые ограничения в качестве наших требований:

  • Этот API должен собирать имя, фамилию, адрес электронной почты, адрес и номер телефона пользователя и сохранять их в БД MySQL.
  • Вызывающие API передают имя, фамилию и адрес электронной почты в качестве обязательных полей. Электронная почта должна быть подтверждена для его формата.
  • Домашний адрес и номер телефона могут быть необязательными.

4. Настройте детали БД:

Для этого проекта вам нужно иметь локальный экземпляр БД MySQL. Вы можете предоставить детали БД в application.properties, как показано ниже.

СВОЙСТВА ПРИМЕНЕНИЯ

1
2
3
spring.datasource.url = jdbc:mysql://localhost:3306/boot_app
spring.datasource.username = root
spring.datasource.password = root

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

СОЗДАТЬ СЦЕНАРИЙ ТАБЛИЦЫ

1
2
3
4
5
6
7
8
create table user_info(
user_id smallint(10) primary key auto_increment,
first_name varchar(150),
last_name varchar(150),
email varchar(200),
address varchar(250),
phone smallint(10)
);

5. Сконфигурируйте Spring Data JPA

Как только таблица будет готова, нам нужно отобразить ее на Java-объект, используя JPA. Каждое поле таблицы отображается в объекте java с использованием аннотаций. Ниже показано, как будет выглядеть наша сущность.

JPA ENTITY

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
package com.example.spring.springrestapp.dao.entity;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
 
import org.springframework.data.annotation.Id;
 
@Entity
@Table(name = "user_info")
public class UserInformation {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer userId;
 
    @Column(name = "FIRST_NAME")
    private String firstName;
  
    @Column(name = "LAST_NAME")
    private String lastName;
  
   @Column(name = "EMAIL")
    private String email;
  
    @Column(name = "ADDRESS")
    private String address;
  
    @Column(name = "PHONE")
    private Integer phone;
 
 
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getAddress() {
        return address;
    }
 
    public void setAddress(String address) {
        this.address = address;
    }
 
    public Integer getPhone() {
        return phone;
    }
 
    public void setPhone(Integer phone) {
        this.phone = phone;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
}

Теперь создайте репозиторий данных JPA Spring. Репозитории JPA данных могут быть созданы путем расширения интерфейса JpaRepository, как показано ниже. (Обратите внимание, что вам нужно передать сущность и тип данных поля идентификатора. В моем примере сущность — это пользователь, а тип поля идентификатора — целое число)

JPA Хранилище

1
2
3
4
5
6
7
package com.example.spring.springrestapp.dao.repo;
 
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.spring.springrestapp.dao.entity.UserInformation;
public interface UserRepo extends JpaRepository {
 
}

Это просто, наш код DAO готов! Spring заботится о создании базовой реализации DAO.

6. Сервис и уровень контроллера

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

СЕРВИС КЛАСС

1
2
3
4
5
6
7
8
9
@Service
public class UserDetailService {
    @Autowired
    private UserRepo userRepo;
     
    public UserInformation saveUser(UserInformation user) {
        return userRepo.save(user);
    }
}

Давайте теперь пойдем и создадим контроллер и метод API. API saveUser принимает данные json в теле запроса и возвращает ответ в виде JSON в теле.

СЛОЙ КОНТРОЛЛЕРА

01
02
03
04
05
06
07
08
09
10
11
12
@RestController
@RequestMapping("/api/user")
public class SpringRestAppController {
 
    @Autowired
    private UserDetailService userService;
 
    @PostMapping(value = "/save")
    public @ResponseBody UserInformation createUser(@RequestBody UserInformation user) {
        return userService.saveUser(user);
    }
}

@RequestMapping используется для отображения ресурсов.
@PostMapping — это то же самое, что HttpPost, назначенный для @RequestMapping .

7. Настройка проверки уровня VO

Наш API нуждается в проверке данных, которые он получает в соответствии с требованиями, упомянутыми в начале. Для этого мы собираемся применить валидацию данных на уровне объекта, как показано ниже.

ПРОВЕРКА ДАННЫХ

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
@Entity
@Table(name = "user_info")
public class UserInformation {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer userId;
 
    @Column(name = "FIRST_NAME")
    @NotBlank(message = "first name can't be blank")
    private String firstName;
  
    @Column(name = "LAST_NAME")
    @NotBlank(message = "last name can't be blank")
    private String lastName;
  
   @Column(name = "EMAIL")
   @NotBlank(message = "email can't be blank")
   @Email(message = "invalid format")
    private String email;
  
    @Column(name = "ADDRESS")
    private String address;
  
    @Column(name = "PHONE")
    private Integer phone;

Обратите внимание, что аннотация @NotBlank не позволяет пустым или нулевым значениям, а @Email проверяет допустимый формат электронной почты. Также мы добавили сообщения для неудачных проверок.

Теперь нам нужно сказать Spring, чтобы он делал валидации в соответствии с аннотациями, указанными в сущности. Для этого мы можем использовать аннотацию @Valid по запросу, как @Valid ниже.

1
2
3
4
@PostMapping(value = "/save")
public @ResponseBody User createUser(@RequestBody @Valid UserInformation user) {
    return userService.saveUser(user);
}

8. Настройте обработку исключений

Если проверка не пройдена, мы должны предоставить правильно отформатированный ответ об ошибке потребителям API. Например, если имя не указано, я хочу вернуть сообщение об ошибке в следующем формате с неверным запросом кода ошибки HTTP. Не стоит давать трассировку стека исключений для пользователей API.

1
2
3
4
{
"errorCode": "VALIDATION_FAILED",
"message": ""
}

Мы можем сделать это в каждом методе контроллера API или создать единую глобальную обработку исключений, чтобы избежать написания дублирующего кода в нескольких местах для одной и той же потребности.
Чтобы обработать центральное исключение в одном месте, мы используем обработчик ошибок Spring. В Spring 3.2 @ControllerAdvice предоставляется для глобализации обработки исключений / ошибок. Чтобы вернуть ответ об ошибке, давайте создадим VO с кодом ошибки и сообщением.

ОБРАЩЕНИЕ С ОШИБКАМИ В ЦЕННОМ ОБЪЕКТЕ

01
02
03
04
05
06
07
08
09
10
11
12
@JsonInclude(content = Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiErrorVO {
 
    private String errorCode;
    private String message;
 
    public ApiErrorVO(String errorCode, String message) {
        super();
        this.errorCode = errorCode;
        this.message = message;
    }

Когда проверка не проходит, Spring создает MethodArgumentNotValidException . Мы можем перехватить это исключение и извлечь сообщение об ошибке из брошенного исключения. Мы используем @ExceptionHandler чтобы перехватить исключение, как @ExceptionHandler ниже.

ОБРАБОТКА ИСКЛЮЧЕНИЙ

01
02
03
04
05
06
07
08
09
10
11
12
13
@ControllerAdvice
public class ApiExceptionHandler {
 
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ApiErrorVO handleValidationError(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        String defaultMessage = fieldError.getDefaultMessage();
        return new ApiErrorVO("VALIDATION_FAILED", defaultMessage);
    }
}

@ResponseStatus используется для указания статуса HTTP Bad request.
@ResponseBody гарантирует, что ошибка будет записана в тело ответа.

9. Вывод

Теперь давайте проверим API.

Случай 1: ошибка проверки
URL: http://localhost:8080/restApp/api/user/save
RequestPayload: обратите внимание на пустое имя

1
2
3
4
5
{
"firstName":"",
"lastName":"K",
}

Ответ: с Http Status 400

1
2
3
4
{
"errorCode": "VALIDATION_FAILED",
"message": "first name can't be blank"
}

Случай 2: когда предоставлены все необходимые значения
Запросить полезную нагрузку:

1
2
3
4
5
{
"firstName":"Alex",
"lastName":"K",
}

Ответ: С Http Status 200

1
2
3
4
5
6
7
8
{
"userId": 8,
"firstName": "Alex",
"lastName": "K",
"email": "[email protected]",
"address": null,
"phoneNumber": null
}

На этом тест заканчивается.

10. Загрузите исходный код

Скачать
Вы можете скачать полный исходный код здесь: SPRING DATA JPA