第 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 中添加配置:

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" />
            &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") 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+'&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 頁面分頁信息。

<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" />
            &nbsp; &nbsp; &nbsp;
            <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 對日期進行了格式化;經過今天的學習較全面演練了前期的學習內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章