第 3-8 课:Spring Data JPA 和 Thymeleaf 综合实践

在前⾯课程中,我们学习了 Spring Boot Web 开发、 JPA 数据库操作、 Thymeleaf 和⻚⾯交互技术,这节课
综合这些内容做⼀个⽤户管理功能,包括展示⽤户列表(分⻚)、添加⽤户、修改⽤户和删除⽤户。有⼈说
程序员的⼀⽣都是在增、删、改、查,这句话不⼀定全对,但也有⼀定的道理,相⽐于这句话,我更认同的
是这句:程序员的技术学习都是从增、删、改、查开始的。
 
这节课将介绍如何使⽤ JPA Thymeleaf 做⼀个⽤户管理功能。

配置信息

添加依赖

 

pom 包⾥⾯添加 JPA Thymeleaf 的相关包引⽤。
<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>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
</dependency>

配置⽂件

application.properties 中添加配置: GitChat
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnico
de=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true
spring.thymeleaf.cache=false
其中, spring.Thymeleaf.cache=false 是关闭 Thymeleaf 的缓存,不然在开发过程中修改⻚⾯不会⽴刻⽣效
需要重启,⽣产可配置为 true
 
在项⽬ resources ⽬录下会有两个⽂件夹: static ⽬录⽤于放置⽹站的静态内容如 css js 、图⽚; templates
⽬录⽤于放置项⽬使⽤的⻚⾯模板。

启动类

启动类需要添加 Servlet 的⽀持:

 

@SpringBootApplication
public class JpaThymeleafApplication extends SpringBootServletInitializer {
 @Override
 protected SpringApplicationBuilder configure(SpringApplicationBuilder applicat
ion) {
 return application.sources(JpaThymeleafApplication.class);
 }
 public static void main(String[] args) throws Exception {
 SpringApplication.run(JpaThymeleafApplication.class, args);
 }
}
添加 SpringBootServletInitializer 是为了⽀持将项⽬打包成独⽴的 war Tomcat 中运⾏的情况。

数据库层

实体类映射数据库表:
 
@Entity
public class User {
 @Id
 @GeneratedValue
 private long id;
 @Column(nullable = false, unique = true)
 private String userName;
 @Column(nullable = false)
 private String passWord;
 @Column(nullable = false)
 private int age;
 @Column(nullable = false)
 private Date regTime;
 //省略getter settet⽅法
}
继承 JpaRepository 类会⾃动实现很多内置的⽅法,包括增、删、改、查,也可以根据⽅法名来⾃动⽣成相
SQL
 
 
public interface UserRepository extends JpaRepository<User, Long> {
 @Query("select u from User u")
 Page<User> findList(Pageable pageable);
 User findById(long id);
 User findByUserName(String userName);
 void deleteById(Long id);
}
Repository 内编写我们需要的 SQL 和分⻚查询。

实现⼀个添加功能

在处理前端业务的时候⼀般是使⽤ param 结尾的参数来处理,在项⽬下新建 param 包,在 param 包下创建
UserParam 类接收添加⽤户的请求参数。另外,需要对接收的参数做校验,按照前⾯课程的内容,引⼊
hibernate-validator 做校验。 GitChat
 
 
 
public class UserParam {
 private long id;
 @NotEmpty(message="姓名不能为空")
 private String userName;
 @NotEmpty(message="密码不能为空")
 @Length(min=6,message="密码⻓度不能⼩于6位")
 private String passWord;
 @Max(value = 100, message = "年龄不能⼤于100岁")
 @Min(value= 18 ,message= "必须年满18岁!" )
 private int age;
 //省略getter settet⽅法
}
Controller 负责接收请求,⾸先判断参数是否正确,如果有错误直接返回⻚⾯,将错误信息展示给⽤户,再判
断⽤户是否存在,如果⽤户已经存在同样返回⻚⾯给出提示。验证通过后,将 UserParam 属性复制到 User
并添加⽤户注册时间,最后将⽤户信息保存到数据库中。
@RequestMapping("/add")
public String add(@Valid UserParam userParam,BindingResult result, Model model) {
 String errorMsg="";
 // 参数校验
 if(result.hasErrors()) {
 List<ObjectError> list = result.getAllErrors();
 for (ObjectError error : list) {
 errorMsg=errorMsg + error.getCode() + "-" + error.getDefaultMessage() 
+";";
 }
 model.addAttribute("errorMsg",errorMsg);
 return "user/userAdd";
 }
 //判断是否重复添加
 User u= userRepository.findByUserName(userParam.getUserName());
 if(u!=null){
 model.addAttribute("errorMsg","⽤户已存在!");
 return "user/userAdd";
 }
 User user=new User();
 BeanUtils.copyProperties(userParam,user);
 user.setRegTime(new Date());
 //保存
 userRepository.save(user);
 return "redirect:/list";
}
  • model 对象主要⽤于传递控制⽅法处理数据到结果⻚⾯;
  • return "redirect:/list"; 代表添加成功后直接跳转到⽤户列表⻚⾯。
添加⽤户部分⻚⾯ userAdd.html
前端⻚⾯引⼊了 Bootstrap 前端框架,以下表单按照 Bootstrap 的格式进⾏设计。
<form class="form-horizontal" th:action="@{/add}" method="post">
 <!-- 表单内容-->
 <div class="form-group">
 <label for="userName" class="col-sm-2 control-label">userName</label>
 <div class="col-sm-10">
 <input type="text" class="form-control" name="userName" id="userName"
 placeholder="userName"/>
 </div>
 </div>
 <div class="form-group">
 <label for="password" class="col-sm-2 control-label" >passWord</label>
 <div class="col-sm-10">
 <input type="password" class="form-control" name="passWord" id="passWo
rd" placeholder="passWord"/>
 </div>
 </div>
 ....
 <!-- 错误信息展示区-->
 <div class="form-group">
 <label class="col-sm-2 control-label"></label>
 <div class="col-sm-10">
 <div th:if="${errorMsg != null}" class="alert alert-danger" role="ale
rt" th:text="${errorMsg}">
 </div>
 </div>
 </div>
 <!-- 按钮区-->
 <div class="form-group">
 <div class="col-sm-offset-2 col-sm-10">
 <input type="submit" value="Submit" class="btn btn-info" />
 &nbsp; &nbsp; &nbsp;
 <input type="reset" value="Reset" class="btn btn-info" />
 &nbsp; &nbsp; &nbsp;
 <a href="/toAdd" th:href="@{/list}" class="btn btn-info">Back</a>
 </div>
 </div>
</form>
效果图:

 

⽤户列表

参考前⾯课程, JPA 依赖 Pageable 为⽤户列表⻚做分⻚,默认每⻚展示 6 个⽤户,并且按照⽤户注册的倒
序来排列,具体信息如下:
 
@RequestMapping("/list")
public String list(Model model,@RequestParam(value = "page", defaultValue = "0") I
nteger page,
 @RequestParam(value = "size", defaultValue = "6") Integer size)
 {
 Sort sort = new Sort(Sort.Direction.DESC, "id");
 Pageable pageable = PageRequest.of(page, size, sort);
 Page<User> users=userRepository.findList(pageable);
 model.addAttribute("users", users);
 return "user/list";
}
  • @RequestParam 常⽤来处理简单类型的绑定,注解有三个属性:valuerequired defaultValuevalue ⽤来指定要传⼊值的 ID 名称,required ⽤来指示参数是否必须绑定,defaultValue 可以设置参数 的默认值。
前端⻚抽取⼀个公共的分⻚信息 ——page.html ,⻚⾯部分信息如下:
<div th:if="${(users.totalPages le 10) and (users.totalPages gt 0)}" th:remove="ta
g">
 <div th:each="pg : ${#numbers.sequence(0, users.totalPages - 1)}" th:remove="t
ag">
 <span th:if="${pg eq users.getNumber()}" th:remove="tag">
 <li class="active"><span class="current_page line_height" th:text=
"${pg+1}">${pageNumber}</span></li>
 </span>
 <span th:unless="${pg eq users.getNumber()}" th:remove="tag">
 <li><a href="#" th:href="@{${pageUrl}(page=${pg})}" th:text="${pg+
1}"></a></li>
 </span>
 </div>
</div>
<li th:if="${users.hasNext()}"><a href="#" th:href="@{${pageUrl}(page=${users.numb
er+1})}">下⼀⻚</a></li>
<li><a href="#" th:href="${users.totalPages le 0 ? pageUrl+'page=0':pageUrl+'&amp;
page='+(users.totalPages-1)}">尾⻚</a></li>
<li><span th:utext="'共'+${users.totalPages}+'⻚ / '+${users.totalElements}+' 条'">
</span></li>
page.html ⻚⾯的作⽤是显示主⻚的⻚码,包括⾸⻚、末⻚、第⼏⻚,共⼏⻚这类信息,需要根据⻚码的数
据进⾏动态调整。⻚⾯中使⽤了 Thymeleaf ⼤量语法: th:if 判断、 th:each 循环、 th:href 链接等,分⻚信息
主要从后端传递的 Page 对象获取。
然后在 list.html ⻚⾯中引⼊ page.html ⻚⾯分⻚信息。 GitChat
<h1>⽤户列表</h1>
<br/><br/>
<div class="with:80%">
 <table class="table table-hover">
 <thead>
 <!-- 表头信息-->
 <tr>
 <th>#</th>
 <th>User Name</th>
 <th>Password</th>
 <th>Age</th>
 <th>Reg Time</th>
 <th>Edit</th>
 <th>Delete</th>
 </tr>
 </thead>
 <tbody>
 <!-- 表循环展示⽤户信息-->
 <tr th:each="user : ${users}">
 <th scope="row" th:text="${user.id}">1</th>
 <td th:text="${user.userName}">neo</td>
 <td th:text="${user.passWord}">Otto</td>
 <td th:text="${user.age}">6</td>
 <td th:text="${#dates.format(user.regTime, 'yyyy/MMM/dd HH:mm:ss')}"><
/td>
 <td><a th:href="@{/toEdit(id=${user.id})}">edit</a></td>
 <td><a th:href="@{/delete(id=${user.id})}" onclick="return confirm('确
认是否删除此⽤户?')" >delete</a></td>
 </tr>
 </tbody>
 </table>
 <!-- 引⼊分⻚内容-->
 <div th:include="page :: pager" th:remove="tag"></div>
</div>
<div class="form-group">
 <div class="col-sm-2 control-label">
 <a href="/toAdd" th:href="@{/toAdd}" class="btn btn-info">add</a>
 </div>
</div>
<tr th:each="user : ${users}"> 这⾥会从 Controler model set 的对象去获取相关的内容,
th:each 表示会循环遍历对象内容。
效果图如下:

修改功能

点击修改功能的时候,需要带上⽤户的 ID 信息:
<td><a th:href="@{/toEdit(id=${user.id})}">edit</a></td>

后端根据⽤户 ID 获取⽤户信息,并放⼊ Model 中.

@RequestMapping("/toEdit")
public String toEdit(Model model,Long id) {
 User user=userRepository.findById(id);
 model.addAttribute("user", user);
 return "user/userEdit";
}
修改⻚⾯展示⽤户信息,以下为 userEdit.html ⻚⾯部分内容: GitChat
 
<form class="form-horizontal" th:action="@{/edit}" th:object="${user}" method="
post">
 <!--隐藏⽤户 ID-->
 <input type="hidden" name="id" th:value="*{id}" />
 <div class="form-group">
 <label for="userName" class="col-sm-2 control-label">userName</label>
 <div class="col-sm-10">
 <input type="text" class="form-control" name="userName" id="userName"
 th:value="*{userName}" placeholder="userName"/>
 </div>
 </div>
 <div class="form-group">
 <label for="password" class="col-sm-2 control-label" >passWord</label>
 <div class="col-sm-10">
 <input type="password" class="form-control" name="passWord" id="passWo
rd" th:value="*{passWord}" placeholder="passWord"/>
 </div>
 </div>
 <!--错误信息-->
 <div class="form-group">
 <label class="col-sm-2 control-label"></label>
 <div class="col-sm-10">
 <div th:if="${errorMsg != null}" class="alert alert-danger" role="ale
rt" th:text="${errorMsg}">
 </div>
 </div>
 </div>
 <!--按钮区-->
 <div class="form-group">
 <div class="col-sm-offset-2 col-sm-10">
 <input type="submit" value="Submit" class="btn btn-info" />
 &nbsp; &nbsp; &nbsp;
 <a th:href="@{/list}" class="btn btn-info">Back</a>
 </div>
 </div>
</form>
修改完成后提交到后台: GitChat
@RequestMapping("/edit")
public String edit(@Valid UserParam userParam, BindingResult result,Model model) {
 String errorMsg="";
 //参数校验
 if(result.hasErrors()) {
 List<ObjectError> list = result.getAllErrors();
 for (ObjectError error : list) {
 errorMsg=errorMsg + error.getCode() + "-" + error.getDefaultMessage() 
+";";
 }
 model.addAttribute("errorMsg",errorMsg);
 model.addAttribute("user", userParam);
 return "user/userEdit";
 }
 //复制属性保持修改后数据
 User user=new User();
 BeanUtils.copyProperties(userParam,user);
 user.setRegTime(new Date());
 userRepository.save(user);
 return "redirect:/list";
}
后台同样需要进⾏参数验证,⽆误后修改对应的⽤户信息。
 
效果图:

删除功能

单击删除按钮的时候需要⽤户再次确认,确认后才能删除。
<td><a th:href="@{/delete(id=${user.id})}" onclick="return confirm('确认是否删除此⽤
户?')" >delete</a></td>
效果如下:
 
后端根据⽤户 ID 进⾏删除即可。
@RequestMapping("/delete")
public String delete(Long id) {
 userRepository.delete(id);
 return "redirect:/list";
}
删除完成之后,再跳转到⽤户列表⻚。

总结

⽤户管理功能包含了⽤户的增加、修改、删除、展示等功能,也是我们⽇常开发中最常⽤的四个功能。在实
现⽤户管理功能的过程中使⽤了 JPA 的增加、修改、删除、查询、分⻚查询功能;使⽤了 Thymeleaf 展示⽤
户信息,在 list ⻚⾯引⼊分⻚模板,使⽤了 Thymeleaf 内嵌的 dates 对⽇期进⾏了格式化;经过今天的学习
较全⾯演练了前期的学习内容。
 

点击这⾥下载源码