我们将讨论的重要主题是处理空值、空字符串和输入验证,这样我们就不会在数据库中输入无效数据。
在处理空值时,我们谈到了 Java 1.8 中引入的 java.util.Optional 的使用。
我们正在为一所大学构建一个 Web 应用程序,允许潜在的学生请求有关他们课程的信息。
查看并下载代码 Github
除了我们典型的 Spring Boot 依赖项之外,我们还使用嵌入式 HSQLDB 数据库和 nekohtml 用于 LEGACYHTML5 模式。
<?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>
在我们的模型中,我们定义:
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; } }
我们定义一个存储库。
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> { }
我们注册 StringTrimmerEditor 以自动将空字符串转换为空值。
当用户发送 POST 请求时,我们希望接收 Student 对象的值,因此我们使用 @ModelAttribute 来做到这一点。
为了确保用户发送的值是有效的,我们接下来使用适当命名的 @Valid 注释。
BindingResult 必须紧随其后,否则用户在提交无效数据时会看到一个错误页面,而不是留在表单页面上。
我们使用 if...else 来控制用户提交表单时发生的情况。如果用户提交无效数据,用户将停留在当前页面,服务器端不会发生任何事情。否则,应用程序将使用用户的数据,用户可以继续。
在这一点上,检查学生的姓名是否为 null 有点多余,但我们这样做了。然后,我们调用下面定义的方法 checkNullString 来查看评论字段是空字符串还是 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);表示String会变成Optional的数据类型,但String可能是null值。
endString = opt.orElse(“None”); 如果变量 opt 为空,则将字符串值设置为“None”。
正如您在上面的控制器映射中看到的,有两个页面。 index.html 是我们的主页,其中包含面向潜在大学生的表单。
我们的主要对象是 Student,所以我们的 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://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=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://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9jb2RlLmpxdWVyeS5jb20vjquery-1.11.1.min.js" integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE=" crossorigin="anonymous"></script> <script src="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=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://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=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://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9jb2RlLmpxdWVyeS5jb20vjquery-1.11.1.min.js" integrity="sha256-VAvG3sHdS5LqTT+5A/aeq/bZGa/Uj04xKxY8KM/w9EE=" crossorigin="anonymous"></script> <script src="https://www.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> </body> </html>
使用 Spring Boot Starter 并包括 Thymeleaf 依赖项,您将自动拥有 /templates/ 的模板位置,并且 Thymeleaf 开箱即用。所以这些设置中的大部分都不需要。
需要注意的一个设置是 nekohtml 提供的 LEGACYHTM5。如果我们愿意,这允许我们使用更随意的 HTML5 标签。否则,Thymeleaf 会非常严格,可能不会解析您的 HTML。例如,如果您不关闭 input 标签,Thymeleaf 将不会解析您的 HTML。
应用程序属性
#================================== # = 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=/
我们到达主页。
我在名称字段和电子邮件字段中输入了无效数据。
现在我在所有字段中输入有效数据,但不提供评论。不需要提供评论。在我们的控制器中,我们将所有空字符串设为空值。如果用户没有提供评论,则 String 值设为“None”。
此演示应用程序演示了如何在 Thymeleaf 表单中验证用户输入。在我看来,Spring 和 Thymeleaf 与 javax.validation.constraints 配合使用可以很好地验证用户输入。 源码在Github
出于演示目的,Java 8 的 Optional 被强加到此应用程序中,我想指出的是,它在使用 @RequestParam 时更有机地工作,如我的 PagingAndSortingRepository 教程 所示。
但是,如果您没有使用 Thymeleaf,您可能会将我们不需要的字段设置为可选。在这里,Vlad Mihalcea 讨论了将可选实体属性映射到 JPA 和 Hibernate 的最佳方式。
标签2: Java教程地址:https://www.cundage.com/article/jcg-validation-thymeleaf-spring.html