在前面課程中,我們學習了 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 中添加配置:
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=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 application) {
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 做校驗。
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="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="alert" 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" />
<input type="reset" value="Reset" class="btn btn-info" />
<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") Integer 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 常用來處理簡單類型的綁定,註解有三個屬性:value、required 和 defaultValue;value 用來指定要傳入值的 ID 名稱,required 用來指示參數是否必須綁定,defaultValue 可以設置參數的默認值。
前端頁抽取一個公共的分頁信息——page.html,頁面部分信息如下:
<div th:if="${(users.totalPages le 10) and (users.totalPages gt 0)}" th:remove="tag">
<div th:each="pg : ${#numbers.sequence(0, users.totalPages - 1)}" th:remove="tag">
<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.number+1})}">下一頁</a></li>
<li><a href="#" th:href="${users.totalPages le 0 ? pageUrl+'page=0':pageUrl+'&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 頁面分頁信息。
<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 頁面部分內容:
<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="passWord" 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="alert" 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" />
<a th:href="@{/list}" class="btn btn-info">Back</a>
</div>
</div>
</form>
修改完成後提交到後臺:
@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 對日期進行了格式化;經過今天的學習較全面演練了前期的學習內容。