总览
我们将要讨论的重要主题涉及空值,空字符串和输入验证,因此我们不会在数据库中输入无效数据。
在处理空值时,我们使用了Java 1.8中引入的java.util.Optional 。
0 – Spring Boot + Thymeleaf示例表单验证应用程序
我们正在为一所大学构建一个Web应用程序,使潜在的学生可以请求有关其课程的信息。
查看并从 Github 下载代码
1 –项目结构
2 –项目依赖性
除了典型的Spring Boot依赖关系之外,我们还在 LEGACYHTML5模式下使用嵌入式HSQLDB数据库和nekohtml 。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 –模型
在我们的模型中,我们定义:
- 自动生成的ID字段
- 名称字段不能为空
- 名称必须在2到40个字符之间
- 由@Email批注验证的电子邮件字段
- 布尔字段“开放日”,允许潜在学生指出她是否想参加开放日
- 布尔字段“订阅”,用于订阅电子邮件更新
- 注释字段是可选的,因此没有最低字符要求,但有最高字符要求
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 –储存库
我们定义一个存储库。
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 ,以查看注释字段是空的String还是null。
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(str); 表示字符串将成为数据类型Optional,但是字符串可以为空值。
endString = opt.orElse(“ None”); 如果变量opt为null,则将String值设置为“ None”。
6 – Thymeleaf模板
正如您在上面的Controller映射中所看到的,有两个页面。 index.html是我们的主页,具有针对潜在大学生的表格。
我们的主要对象是学生,因此我们的th:object当然是指该对象 。 我们模型的字段分别进入th:field 。
我们将表单的输入包装在表格中以进行格式化。
在每个表单元格(td)下,我们都有一个条件语句,如下所示: […] th:if =” $ {#fields.hasErrors('name')}“ th:errors =” * {name}”
[…]
上面的条件语句意味着,如果用户在该字段中输入的数据与我们在Student模型中对该字段的要求不符,然后提交表单,则在用户返回此页面时显示输入要求。
index.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<!-- CSS INCLUDE -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
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>
<script src="https://code.jquery.com/jquery-1.11.1.min.js"
integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE="
crossorigin="anonymous"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
</body>
</html>
这是用户成功完成表单后看到的页面。 我们使用th:text向用户显示他或她在该字段中输入的文本。
Thanks.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<!-- CSS INCLUDE -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
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
src="https://code.jquery.com/jquery-1.11.1.min.js"
integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE="
crossorigin="anonymous"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
</body>
</html>
7 –配置
使用Spring Boot Starter并包括Thymeleaf依赖项,您将自动拥有/ templates /的模板位置,并且Thymeleaf可以直接使用。 因此,不需要大多数这些设置。
要注意的一个设置是nekohtml提供的LEGACYHTM5 。 如果需要,这使我们可以使用更多随意HTML5标签。 否则,Thymeleaf将非常严格,并且可能无法解析您HTML。 例如,如果您不关闭输入标签,Thymeleaf将不会解析您HTML。
application.properties
#==================================
# = 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的Optional出于某种演示目的而被强加到该应用程序中,我想指出的是,当使用@RequestParam时 ,如我的PagingAndSortingRepository教程中所示,它可以更有机地工作。
但是,如果您不使用Thymeleaf,则可以将我们不需要的字段设置为Optional 。 Vlad Mihalcea在这里讨论了使用JPA和Hibernate映射Optional实体属性的最佳方法 。
翻译自: https://www.javacodegeeks.com/2017/10/validation-thymeleaf-spring.html