Spring Boot CRUD Application with Thymeleaf – 使用Thymeleaf的Spring Boot CRUD应用

最后修改: 2018年 10月 31日

1. Overview


The implementation of DAO layers that provide CRUD functionality on JPA entities can be a repetitive, time-consuming task that we want to avoid in most cases.


Luckily, Spring Boot makes it easy to create CRUD applications through a layer of standard JPA-based CRUD repositories.

幸运的是,Spring Boot使我们能够通过一层基于JPA的标准CRUD库轻松创建CRUD应用程序。

In this tutorial, we’ll learn how to develop a CRUD web application with Spring Boot and Thymeleaf.

在本教程中,我们将学习如何使用Spring Boot和Thymeleaf开发一个CRUD Web应用程序。

2. The Maven Dependencies


In this case, we’ll rely on spring-boot-starter-parent for simple dependency management, versioning and plugin configuration.


As a result, we won’t need to specify the versions of the project dependencies in our pom.xml file, except for overriding the Java version:



3. The Domain Layer


With all the project dependencies already in place, let’s now implement a naive domain layer.


For simplicity’s sake, this layer will include one single class that will be responsible for modeling User entities:


public class User {
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @NotBlank(message = "Name is mandatory")
    private String name;
    @NotBlank(message = "Email is mandatory")
    private String email;

    // standard constructors / setters / getters / toString

Let’s keep in mind that we’ve annotated the class with the @Entity annotation. Therefore, the JPA implementation, which is Hibernate, in this case, will be able to perform CRUD operations on the domain entities. For an introductory guide to Hibernate, visit our tutorial on Hibernate 5 with Spring.

让我们记住,我们已经用@Entity注解对该类进行了注解。因此,在这种情况下,JPA 实现(即 Hibernate)将能够对域实体执行 CRUD 操作。有关 Hibernate 的介绍性指南,请访问我们关于Hibernate 5 with Spring 的教程。

In addition, we’ve constrained the name and email fields with the @NotBlank constraint. This implies that we can use Hibernate Validator for validating the constrained fields before persisting or updating an entity in the database.

此外,我们已经用@NotBlank约束来约束nameemail字段。这意味着我们可以在持久化或更新数据库中的实体之前使用Hibernate Validator来验证受约束的字段。

For the basics on this, check out our associated tutorial on Bean Validation.

关于这方面的基础知识,请查看我们关于Bean Validation的相关教程

4. The Repository Layer


At this point, our sample web application does nothing. But that’s about to change.


Spring Data JPA allows us to implement JPA-based repositories (a fancy name for the DAO pattern implementation) with minimal fuss.

Spring Data JPA允许我们以最小的代价实现基于JPA的存储库(DAO模式实现的一个花哨的名字)。

Spring Data JPA is a key component of Spring Boot’s spring-boot-starter-data-jpa that makes it easy to add CRUD functionality through a powerful layer of abstraction placed on top of a JPA implementation. This abstraction layer allows us to access the persistence layer without having to provide our own DAO implementations from scratch.

Spring Data JPA是Spring Boot的spring-boot-starter-data-jpa的一个关键组件,通过置于JPA实现之上的强大抽象层,可以轻松添加CRUD功能。这个抽象层允许我们访问持久化层,而不需要从头开始提供我们自己的DAO实现。

To provide our application with basic CRUD functionality on User objects, we just need to extend the CrudRepository interface:


public interface UserRepository extends CrudRepository<User, Long> {}

And that’s it! By extending the CrudRepository interface, Spring Data JPA will provide implementations for the repository’s CRUD methods for us.

就这样了!通过扩展CrudRepository接口,Spring Data JPA将为我们提供存储库的CRUD方法的实现。

5. The Controller Layer


Thanks to the layer of abstraction that spring-boot-starter-data-jpa places on top of the underlying JPA implementation, we can easily add some CRUD functionality to our web application through a basic web tier.


In our case, a single controller class will suffice for handling GET and POST HTTP requests and then mapping them to calls to our UserRepository implementation.

在我们的案例中,一个控制器类就足以处理GET和POST HTTP请求,然后将它们映射到我们的UserRepository实现。

The controller class relies on some of Spring MVC’s key features. For a detailed guide on Spring MVC, check out our Spring MVC tutorial.

该控制器类依赖于Spring MVC的一些关键特性。关于Spring MVC的详细指南,请查看我们的Spring MVC教程

Let’s start with the controller’s showSignUpForm() and addUser() methods.


The former will display the user signup form, while the latter will persist a new entity in the database after validating the constrained fields.


If the entity doesn’t pass the validation, the signup form will be redisplayed.


Otherwise, once the entity has been saved, the list of persisted entities will be updated in the corresponding view:


public class UserController {
    public String showSignUpForm(User user) {
        return "add-user";
    public String addUser(@Valid User user, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "add-user";
        return "redirect:/index";

    // additional CRUD methods

We’ll also need a mapping for the /index URL:

我们还需要一个/index URL的映射。

public String showUserList(Model model) {
    model.addAttribute("users", userRepository.findAll());
    return "index";

Within the UserController, we will also have the showUpdateForm() method, which is responsible for fetching the User entity that matches the supplied id from the database.


If the entity exists, it will be passed on as a model attribute to the update form view.


So, the form can be populated with the values of the name and email fields:


public String showUpdateForm(@PathVariable("id") long id, Model model) {
    User user = userRepository.findById(id)
      .orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
    model.addAttribute("user", user);
    return "update-user";

Finally, we have the updateUser() and deleteUser() methods within the UserController class.


The first one will persist the updated entity in the database, while the last one will remove the given entity.


In either case, the list of persisted entities will be updated accordingly:


public String updateUser(@PathVariable("id") long id, @Valid User user, 
  BindingResult result, Model model) {
    if (result.hasErrors()) {
        return "update-user";
    return "redirect:/index";
public String deleteUser(@PathVariable("id") long id, Model model) {
    User user = userRepository.findById(id)
      .orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
    return "redirect:/index";

6. The View Layer


At this point, we’ve implemented a functional controller class that performs CRUD operations on User entities. Even so, there’s still a missing component in this schema: the view layer.


Under the src/main/resources/templates folder, we need to create the HTML templates required for displaying the signup form and the update form as well as rendering the list of persisted User entities.


As stated in the introduction, we’ll use Thymeleaf as the underlying template engine for parsing the template files.


Here’s the relevant section of the add-user.html file:


<form action="#" th:action="@{/adduser}" th:object="${user}" method="post">
    <label for="name">Name</label>
    <input type="text" th:field="*{name}" id="name" placeholder="Name">
    <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
    <label for="email">Email</label>
    <input type="text" th:field="*{email}" id="email" placeholder="Email">
    <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
    <input type="submit" value="Add User">   

Notice how we’ve used the @{/adduser} URL expression to specify the form’s action attribute and the ${} variable expressions for embedding dynamic content in the template, such as the values of the name and email fields and the post-validation errors.

注意我们是如何使用@{/adduser} URL表达式来指定表单的action属性和${}变量表达式来嵌入模板中的动态内容,例如nameemail字段的值和验证后的错误。

Similar to add-user.html, here’s how the update-user.html template looks:


<form action="#" 
    <label for="name">Name</label>
    <input type="text" th:field="*{name}" id="name" placeholder="Name">
    <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
    <label for="email">Email</label>
    <input type="text" th:field="*{email}" id="email" placeholder="Email">
    <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
    <input type="submit" value="Update User">   

Finally, we have the index.html file that displays the list of persisted entities along with the links for editing and removing existing ones:


<div th:switch="${users}">
    <h2 th:case="null">No users yet!</h2>
        <div th:case="*">
                <tr th:each="user : ${users}">
                    <td th:text="${user.name}"></td>
                    <td th:text="${user.email}"></td>
                    <td><a th:href="@{/edit/{id}(id=${user.id})}">Edit</a></td>
                    <td><a th:href="@{/delete/{id}(id=${user.id})}">Delete</a></td>
    <p><a href="/signup">Add a new user</a></p>

For simplicity’s sake, the templates look rather skeletal and only provide the required functionality without adding unnecessary cosmetics.


To give the templates an improved, eye-catching look without spending too much time on HTML/CSS, we can easily use a free Twitter Bootstrap UI kit, such as Shards.

为了使模板具有改进的、醒目的外观,而无需在HTML/CSS上花费太多时间,我们可以轻松地使用免费的Twitter BootstrapUI工具包,例如Shards

7. Running the Application


Finally, let’s define the application’s entry point.


Like most Spring Boot applications, we can do this with a plain old main() method:

像大多数Spring Boot应用程序一样,我们可以用一个普通的main()方法来实现。

public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

Now let’s hit “Run” in our IDE and then open up our browser and point it to http://localhost:8080.

现在,让我们在IDE中点击 “运行”,然后打开我们的浏览器,将其指向http://localhost:8080

If the build has successfully compiled, we should see a basic CRUD user dashboard with links for adding new entities and for editing and removing existing ones.


8. Conclusion


In this article, we learned how to build a basic CRUD web application with Spring Boot and Thymeleaf.

在这篇文章中,我们学习了如何用Spring Boot和Thymeleaf构建一个基本的CRUD网络应用。

As usual, all the code samples shown in the article are available over on GitHub.