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

最后修改: 2018年 10月 31日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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.

实现DAO层,在JPA实体上提供CRUD功能可能是一项重复的、耗时的任务,在大多数情况下我们希望避免这种情况。

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

2.Maven的依赖性

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

在这种情况下,我们将依靠spring-boot-starter-parent进行简单的依赖管理、版本管理和插件配置。

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:

因此,我们不需要在pom.xml文件中指定项目依赖的版本,除了覆盖Java的版本。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</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-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
</dependencies>

3. The Domain Layer

3.领域层

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:

为了简单起见,这一层将包括一个单一的类,它将负责为用户实体建模。

@Entity
public class User {
    
    @Id
    @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

4.存储库层

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:

为了给我们的应用程序提供对User对象的基本CRUD功能,我们只需要扩展CrudRepository接口。

@Repository
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

5.控制器层

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.

由于spring-boot-starter-data-jpa放置在底层JPA实现之上的抽象层,我们可以通过一个基本的Web层轻松地为我们的Web应用程序添加一些CRUD功能。

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.

让我们从控制器的showSignUpForm()addUser()方法开始。

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:

否则,一旦实体被保存,持久化的实体列表就会在相应的视图中被更新。

@Controller
public class UserController {
    
    @GetMapping("/signup")
    public String showSignUpForm(User user) {
        return "add-user";
    }
    
    @PostMapping("/adduser")
    public String addUser(@Valid User user, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "add-user";
        }
        
        userRepository.save(user);
        return "redirect:/index";
    }

    // additional CRUD methods
}

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

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

@GetMapping("/index")
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.

UserController中,我们还将有showUpdateForm()方法,它负责从数据库中获取符合所提供的idUser实体。

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:

因此,该表格可以用姓名电子邮件字段的值来填充。

@GetMapping("/edit/{id}")
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.

最后,我们在UserController类中有updateUser()deleteUser()方法。

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:

在这两种情况下,持久化实体的列表将被相应地更新。

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

6. The View Layer

6.视图层

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.

在这一点上,我们已经实现了一个功能性的控制器类,对用户实体执行CRUD操作。即便如此,这个模式中仍然缺少一个组件:视图层。

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.

src/main/resources/templates文件夹下,我们需要创建显示注册表和更新表,以及渲染持久化的User实体列表所需的HTML模板。

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

正如介绍中所说,我们将使用Thymeleaf作为底层模板引擎来解析模板文件。

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

这里是add-user.html文件的相关部分。

<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">   
</form>

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:

add-user.html类似,下面是update-user.html模板的样子。

<form action="#" 
  th:action="@{/update/{id}(id=${user.id})}" 
  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="Update User">   
</form>

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

最后,我们有一个index.html文件,显示持久化实体的列表,以及用于编辑和删除现有实体的链接。

<div th:switch="${users}">
    <h2 th:case="null">No users yet!</h2>
        <div th:case="*">
            <h2>Users</h2>
            <table>
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Email</th>
                        <th>Edit</th>
                        <th>Delete</th>
                    </tr>
                </thead>
                <tbody>
                <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>
                </tr>
            </tbody>
        </table>
    </div>      
    <p><a href="/signup">Add a new user</a></p>
</div>

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

7.运行应用程序

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()方法来实现。

@SpringBootApplication
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.

如果编译成功,我们应该看到一个基本的CRUD用户仪表板,上面有添加新实体以及编辑和删除现有实体的链接。

8. Conclusion

8.结论

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.

像往常一样,文章中显示的所有代码样本都可以在GitHub上找到。