Data JPA 和 Thymeleaf 綜合實踐
1 第3-8課:Spring Data JPA 和 Thymeleaf 綜合實踐
在前面課程中,我們學習了 Spring Boot Web 開發、JPA 數據庫操作、Thymeleaf 和頁面交互技術,這節課綜合這些內容做一個用戶管理功能,包括展示用戶列表(分頁)、添加用戶、修改用戶和刪除用戶。有人說程序員的一生都是在增、刪、改、查,這句話不一定全對,但也有一定的道理,相比於這句話,我更認同的是這句:程序員的技術學習都是從增、刪、改、查開始的。
這節課將介紹如何使用 JPA 和 Thymeleaf 做一個用戶管理功能。
1.1 配置信息
1.1.1 添加依賴
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>
1.1.2 配置文件
在 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 目錄用於放置項目使用的頁面模板。
1.1.3 啟動類
啟動類需要添加 Servlet 的支持:
@SpringBootApplication
publicclass 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 中運行的情況。
1.2 數據庫層
實體類映射數據庫表:
@Entity
publicclass 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。
publicinterface 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 和分頁查詢。
1.3 實現一個添加功能
在處理前端業務的時候一般是使用 param 結尾的參數來處理,在項目下新建 param 包,在 param 包下創建 UserParam 類接收添加用戶的請求參數。另外,需要對接收的參數做校驗,按照前面課程的內容,引入 hibernate-validator 做校驗。
publicclass 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=
newUser();
BeanUtils.copyProperties(userParam,user);
user.setRegTime(
newDate());
//保存
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>
效果圖:
1.4 用戶列表
參考前面課程,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 =
newSort(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 表示會循環遍歷對象內容。
效果圖如下:
1.5 修改功能
點擊修改功能的時候,需要帶上用戶的 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=
newUser();
BeanUtils.copyProperties(userParam,user);
user.setRegTime(
newDate());
userRepository.save(user);
return
"redirect:/list"
;
}
後臺同樣需要進行參數驗證,無誤後修改對應的用戶信息。
效果圖:
1.6 刪除功能
單擊刪除按鈕的時候需要用戶再次確認,確認後才能刪除。
<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"
;
}
刪除完成之後,再跳轉到用戶列表頁。
1.7 總結
用戶管理功能包含了用戶的增加、修改、刪除、展示等功能,也是我們日常開發中最常用的四個功能。在實現用戶管理功能的過程中使用了 JPA 的增加、修改、刪除、查詢、分頁查詢功能;使用了 Thymeleaf 展示用戶信息,在 list 頁面引入分頁模板,使用了 Thymeleaf 內嵌的 dates 對日期進行了格式化;經過今天的學習較全面演練了前期的學習內容。
點擊這裏下載源碼。
Data JPA 和 Thymeleaf 綜合實踐