Статьи

Валидация в Thymeleaf + Spring

обзор

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

При работе с нулевыми значениями мы коснемся использования java.util.Optional, который был представлен в Java 1.8.

0 — Пример проверки формы Spring Boot + Thymeleaf

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

Посмотреть и скачать код с Github

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

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

Помимо наших типичных зависимостей Spring Boot, мы используем встроенную базу данных HSQLDB и nekohtml для режима LEGACYHTML5.

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
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.michaelcgood</groupId>
    <artifactId>michaelcgood-validation-thymeleaf</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
 
    <name>michaelcgood-validation-thymeleaf</name>
    <description>Michael C  Good - Validation in Thymeleaf Example Application</description>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
 
    <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-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- legacy html allow -->
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.21</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
 
</project>

3 — Модель

В нашей модели мы определяем:

  • Сгенерированное автоматически поле идентификатора
  • Поле имени, которое не может быть пустым
  • Что имя должно быть от 2 до 40 символов
  • Поле электронной почты, которое проверяется аннотацией @Email
  • Булево поле «openhouse», которое позволяет потенциальному студенту указать, хочет ли он посещать день открытых дверей
  • Логическое поле «подписаться» для подписки на обновления по электронной почте
  • Поле комментариев, которое является необязательным, поэтому не требуется минимальное требование к символу, но есть максимальное требование к символу
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
package com.michaelcgood.model;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
import org.hibernate.validator.constraints.Email;
 
@Entity
public class Student {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NotNull
    @Size(min=2, max=40)
    private String name;
    @NotNull
    @Email
    private String email;
    private Boolean openhouse;
    private Boolean subscribe;
     @Size(min=0, max=300)
    private String  comments;
     
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public Boolean getOpenhouse() {
        return openhouse;
    }
    public void setOpenhouse(Boolean openhouse) {
        this.openhouse = openhouse;
    }
    public Boolean getSubscribe() {
        return subscribe;
    }
    public void setSubscribe(Boolean subscribe) {
        this.subscribe = subscribe;
    }
    public String getComments() {
        return comments;
    }
    public void setComments(String comments) {
        this.comments = comments;
    }
     
 
}

4 — Репозиторий

Мы определяем хранилище.

01
02
03
04
05
06
07
08
09
10
11
package com.michaelcgood.dao;
 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
import com.michaelcgood.model.Student;
 
@Repository
public interface StudentRepository extends JpaRepository<Student,Long> {
 
}

5 — Контроллер

Мы регистрируем StringTrimmerEditor для автоматического преобразования пустых строк в нулевые значения.

Когда пользователь отправляет запрос POST, мы хотим получить значение этого объекта Student, поэтому для этого мы используем @ModelAttribute .

Чтобы убедиться, что пользователь отправляет допустимые значения, мы используем соответствующую аннотацию @Valid .

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

Мы используем if … else для контроля того, что происходит, когда пользователь отправляет форму. Если пользователь отправляет неверные данные, он останется на текущей странице, и больше ничего не произойдет на стороне сервера. В противном случае приложение будет использовать данные пользователя, и пользователь сможет продолжить.

На данный момент, это несколько избыточно, чтобы проверить, является ли имя студента нулевым, но мы делаем. Затем мы вызываем метод checkNullString , который определен ниже, чтобы увидеть, является ли поле комментария пустой строкой или пустым.

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
87
88
89
90
91
package com.michaelcgood.controller;
 
import java.util.Optional;
 
import javax.validation.Valid;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.michaelcgood.dao.StudentRepository;
import com.michaelcgood.model.Student;
 
@Controller
public class StudentController {
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
    }
    public String finalString = null;
    @Autowired
    private StudentRepository studentRepository;
    @PostMapping(value="/")
    public String addAStudent(@ModelAttribute @Valid Student newStudent, BindingResult bindingResult, Model model){
        if (bindingResult.hasErrors()) {
            System.out.println("BINDING RESULT ERROR");
            return "index";
        } else {
            model.addAttribute("student", newStudent);
 
            if (newStudent.getName() != null) {
                try {
                    // check for comments and if not present set to 'none'
                    String comments = checkNullString(newStudent.getComments());
                    if (comments != "None") {
                        System.out.println("nothing changes");
                    } else {
                        newStudent.setComments(comments);
                    }
                } catch (Exception e) {
 
                    System.out.println(e);
 
                }
                studentRepository.save(newStudent);
                System.out.println("new student added: " + newStudent);
            }
 
            return "thanks";
        }
    }
     
    @GetMapping(value="thanks")
    public String thankYou(@ModelAttribute Student newStudent, Model model){
        model.addAttribute("student",newStudent);
         
        return "thanks";
    }
     
    @GetMapping(value="/")
    public String viewTheForm(Model model){
        Student newStudent = new Student();
        model.addAttribute("student",newStudent);
        return "index";
    }
     
    public String checkNullString(String str){
        String endString = null;
        if(str == null || str.isEmpty()){
            System.out.println("yes it is empty");
            str = null;
            Optional<String> opt = Optional.ofNullable(str);
            endString = opt.orElse("None");
            System.out.println("endString : " + endString);
        }
        else{
            ; //do nothing
        }
         
         
        return endString;
         
    }
 
}

Optional.ofNullable (ул); означает, что String станет типом данных Optional, но String может иметь нулевое значение.

endString = opt.orElse («Нет»); устанавливает значение String равным «None», если переменная opt равна нулю.

6 — Шаблоны из тимьяна

Как вы видели в отображении нашего контроллера выше, есть две страницы. Index.html — это наша главная страница, которая имеет форму для потенциальных студентов университета.

Нашим основным объектом является Student, поэтому, конечно, наш объект th: относится к этому. Поля нашей модели соответственно идут в th: field .

Мы обертываем входные данные нашей формы в таблицу для форматирования.

Ниже каждой ячейки таблицы (td) у нас есть условный оператор, подобный этому: […] th: if = ”$ {# fields.hasErrors (‘name’)}” th: errors = ”* {name}»
[…]

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

index.html

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
    xmlns:th="http://www.thymeleaf.org">
 
<head>
<!-- CSS INCLUDE -->
<link rel="stylesheet"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
    crossorigin="anonymous"></link>
 
<!-- EOF CSS INCLUDE -->
</head>
<body>
 
    <!-- START PAGE CONTAINER -->
    <div class="container-fluid">
        <!-- PAGE TITLE -->
        <div class="page-title">
            <h2>
                <span class="fa fa-arrow-circle-o-left"></span> Request University
                Info
            </h2>
        </div>
        <!-- END PAGE TITLE -->
        <div class="column">
            <form action="#" th:action="@{/}" th:object="${student}"
                method="post">
                <table>
                    <tr>
                        <td>Name:</td>
                        <td><input type="text" th:field="*{name}"></input></td>
                        <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name
                            Error</td>
                    </tr>
                    <tr>
                        <td>Email:</td>
                        <td><input type="text" th:field="*{email}"></input></td>
                        <td th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email
                            Error</td>
                    </tr>
                    <tr>
                        <td>Comments:</td>
                        <td><input type="text" th:field="*{comments}"></input></td>
                    </tr>
                    <tr>
                        <td>Open House:</td>
                        <td><input type="checkbox" th:field="*{openhouse}"></input></td>
                 
                    </tr>
                    <tr>
                        <td>Subscribe to updates:</td>
                        <td><input type="checkbox" th:field="*{subscribe}"></input></td>
                 
                    </tr>
                    <tr>
                        <td>
                            <button type="submit" class="btn btn-primary">Submit</button>
                        </td>
                    </tr>
                </table>
            </form>
 
        </div>
        <!-- END PAGE CONTENT -->
        <!-- END PAGE CONTAINER -->
    </div>
        integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE="
        crossorigin="anonymous"></script>
 
 
    <script
        integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
        crossorigin="anonymous"></script>
 
 
</body>
</html>

Здесь у нас есть страница, которую видит пользователь, когда он успешно заполнил форму. Мы используем th: text, чтобы показать пользователю текст, который он или она вводит для этого поля.

thanks.html

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
    xmlns:th="http://www.thymeleaf.org">
 
<head>
<!-- CSS INCLUDE -->
<link rel="stylesheet"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
    crossorigin="anonymous"></link>
 
<!-- EOF CSS INCLUDE -->
 
 
</head>
<body>
 
    <!-- START PAGE CONTAINER -->
    <div class="container-fluid">
 
        <!-- PAGE TITLE -->
        <div class="page-title">
            <h2>
                <span class="fa fa-arrow-circle-o-left"></span> Thank you
            </h2>
        </div>
        <!-- END PAGE TITLE -->
        <div class="column">
            <table class="table datatable">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Email</th>
                        <th>Open House</th>
                        <th>Subscribe</th>
                        <th>Comments</th>
                    </tr>
                </thead>
                <tbody>
                    <tr th:each="student : ${student}">
                        <td th:text="${student.name}">Text ...</td>
                        <td th:text="${student.email}">Text ...</td>
                        <td th:text="${student.openhouse}">Text ...</td>
                        <td th:text="${student.subscribe}">Text ...</td>
                        <td th:text="${student.comments}">Text ...</td>
                    </tr>
                </tbody>
            </table>
        </div>   
        </div>
        <!-- END PAGE CONTAINER -->
    </div>
        <script
  integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE="
  crossorigin="anonymous"></script>
  
 
    <script
        integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
        crossorigin="anonymous"></script>
 
</body>
</html>

7 — Конфигурация

Используя Spring Boot Starter и включая зависимости Thymeleaf, у вас автоматически будет расположение шаблонов / templates / , а Thymeleaf просто работает из коробки. Так что большинство этих настроек не нужны.

Один параметр, на который следует обратить внимание — это LEGACYHTM5, предоставленный nekohtml . Это позволяет нам использовать более случайные теги HTML5, если мы хотим. В противном случае Thymeleaf будет очень строгим и может не проанализировать ваш HTML. Например, если вы не закроете входной тег, Thymeleaf не будет анализировать ваш HTML.

application.properties

01
02
03
04
05
06
07
08
09
10
11
#==================================
# = Thymeleaf configurations
#==================================
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.thymeleaf.mode=LEGACYHTML5
 
server.contextPath=/

8 — Демо

Главная страница

Здесь мы приходим на главную страницу.

Неверные данные

Я ввожу неверные данные в поле имени и адрес электронной почты.

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

Теперь я размещаю достоверные данные во всех полях, но не предоставляю комментарий. Не требуется предоставлять комментарий. В нашем контроллере мы сделали все пустые строки пустыми значениями. Если пользователь не предоставил комментарий, значение String устанавливается как «None».

9 — Заключение

Заворачивать

Это демонстрационное приложение продемонстрировало, как проверять пользовательский ввод в форме Thymeleaf.
На мой взгляд, Spring и Thymeleaf хорошо работают с javax.validation.constraints для проверки ввода пользователя.
Исходный код есть на Github

Заметки

Необязательный Java 8 был своего рода принудительным в этом приложении для демонстрационных целей, и я хочу отметить, что он работает более органично при использовании @RequestParam, как показано в моем руководстве PagingAndSortingRepository .

Однако, если вы не используете Thymeleaf, вы могли бы сделать наши необязательные поля необязательными . Здесь Влад Михалча обсуждает лучший способ сопоставить атрибут необязательной сущности с JPA и Hibernate .

Опубликовано на Java Code Geeks с разрешения Майкла Гуда, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Проверка в Thymeleaf + Spring

Мнения, высказанные участниками Java Code Geeks, являются их собственными.