SpringBoot模擬項目綜合實踐(活動創建-Hi現場)


模擬hi現場互聯網項目的活動業務功能:hixianchang.com

這個軟件上可以實現互動答疑功能,但是如果會議人數超過300人就需要收費:

1.首先點擊登錄,會彈出一個登錄的模態框,這個模塊框如何實現:

2.登錄以后,點擊創建活動,這個創建活動的按鈕如何實現: 

3.活動的基本信息頁面如何實現:

4.活動生效時間默認創建即生效,只允許選擇活動失效時間,這個時間如何實現: 

5.活動類型為下拉選項,這個下拉選項如何實現:

6.基本信息輸入完成,創建活動就會把活動信息寫入數據庫,這個功能如何實現:

一.搭建項目初始環境:

1.初始化數據庫數據(命令行執行): 

1)登錄Mysql客戶端:mysql -uroot -proot
2)創建數據庫dbactivity:create database dbactivity;或者create database if not exists dbactivity charset utf8;
3)查看數據庫:show databases;
4)設置客戶端編碼:set names utf8;或者 set charset utf8;(如果要想識別utf8的文件,建議都是utf8的編碼,一定要設置,因為導入sql文件的編碼是utf8);
5)執行activity.sql文件,導入數據表文件activity.sql: source d:/activity.sql
6)打開數據庫dbactivity:use dbactivity;
7)查看數據庫的所有表:show tables;
8)查看數據庫的某表結構:desc tb_activity;

2.創建SpringBoot Maven項目:

1) 官網訪問超時:

 

 2)創建項目:

3)搜索、選擇依賴(可能有些依賴是無法搜索到的,因為這里搜索的是spring官網提供的依賴庫中的資源,如果資源庫沒有就無法搜索到):

注:在pom.xml文件中,如果第一次添加整合mybatis依賴的時候,由於spring官網資源庫,默認是沒有指定mybatis的版本,會有問題,需要自己指定一個,以后再次添加時sts會就會有版本記錄,無需再次指定。

4)整合HikariCP連接池時,用的是MySQL Driver和JDBC API;整合MyBatis時,用的是MyBatis Framework;整合SpringMVC時,用的是Spring Web和Thymeleaf:

5)創建后的項目結構如下:后面會在stemplates目錄里 再創建一個目錄modules(當然其他名字也可以,與thymeleaf配置對應即可),模塊目錄。

注:如果在創建項目時,沒有直接添加Spring Web和Thymeleaf,而是在已創建好的項目中引入這兩個依賴,則需要自己手動構建目錄結構,即在src/main/resources下添加static和templates這兩個目錄。

3.修改application.properties文件,進行資源配置:

1)添加數據源配置(使用內置的HikariCP連接池)

2)添加mybatis配置

3)添加thymeleaf配置

4)添加日志配置

代碼如下:

#spring datasource

spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

#spring mybatis
mybatis.mapper-locations=classpath:/mapper/*/*.xml

#spring thymeleaf/spring web

spring.thymeleaf.prefix=classpath:/templates/modules/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false

#spring logging
logging.level.com.cy=debug

4.引入前端開發框架Bootstrap的CSS樣式與JS腳本文件至項目結構

1)訪問Bootstrap官網:https://www.bootcss.com/ 

2)點擊Bootstrap3中文文檔(v3.3.7),進入如下頁面:

 

3)點擊下載 Bootstrap,進入頁面,在用於生產環境的Bootstrap,點擊下載Bootstrap即可下載。

4) 將下載下來的 bootstrap-3.3.7-dist.zip 解壓到一個目錄中,然后把這個解壓后的資源重命名去掉版本號,就命名為bootstrap,名字沒有必要這么長:

5)這個bootstrap是別人寫好的資源,我們需要用,所以就把bootstrap整個的這個目錄,復制粘貼到項目src/main/resources目錄下的static目錄下:

注:bootstrap目錄名盡量不要有什么版本信息,因為可能在html中引入資源時,可能需要對名字做額外的符號處理(比如下面官方文檔的基本模板中版本號之前的@符號處理),這個目錄下又包含css、fonts和js,這3個目錄。

6)如何使用bootstrap,我們可以從官網https://www.bootcss.com/ 的Bootstrap3中文文檔選項(我們下載是Bootstrap3版本):

7)在Bootstrap3中文文檔中,上方有一個起步選項,它的下面有一個內容是 基本模板:

 

8)基本模板的內容是一段html模擬案例,其第一部分在head標簽下,有link標簽是對bootstrap.min.css的引入:

9)基本模板的內容的第二部分,在body區的最下方,有兩個script標簽,分別是對jquery.min.js和bootstrap.min.js的引入:

注:正如注釋中所描述,jquery必須放到前面,因為bootstrap這個前端框架的js,它內部的一些函數是依賴於jquery的。

10)從Bootstrap官網中也可以見得。Bootstrap是依賴於jquery的:

11)引入jquery.min.js到項目目錄src/main/resources/static下(jquery可以從官網 https://jquery.com/ 下載,或者用自己已經備份好的資源庫找到):

 

12)在src/main/resources/static目錄下,再創建一個目錄jquery,然后把 jquery.min.js 拷貝到這個目錄中:

 

注:bootstrap是從官網https://www.bootcss.com/ 上下載下來的,jquery也可以從官網去下載,但我們程序員都有自己的資源庫,是之前下載好了的。

13)同時我們在templates下面創建一個目錄modules模塊(這里不寫成pages,目錄名可以自定義);然后在src/main/resources目錄下,再創建一個文件夾mapper/activity,表示活動模塊,后面會用來放springmvc配置的sql映射文件,結構如下:

5.引入前端開發框架Bootstrap的CSS樣式與JS腳本文件至html頁面

1)先以goods.html頁面為例,根據Bootstrap官網的基本模板,引入Bootstrap前端開發框架,基本模板示例如下:

基本模板
使用以下給出的這份超級簡單的 HTML 模版,或者修改這些實例。我們強烈建議你對這些實例按照自己的需求進行修改,而不要簡單的復制、粘貼。
拷貝並粘貼下面給出的 HTML 代碼,這就是一個最簡單的 Bootstrap 頁面了。

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支持 HTML5 元素和媒體查詢(media queries)功能 -->
    <!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <h1>你好,世界!</h1>

    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

2)根據模板示例, bootstrap.min.css 文件引入head標簽里面:

<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> 改為:
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">//因為在項目static目錄下,默認為項目根目錄,端口號下面,所以以斜杠開頭

 注:https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist,其實用這個地址也可以,這個地址是個網絡地址,不過一旦這個網絡不可訪問的時候,此css就無法引入;

這里已經把Bootstrap拷貝到項目里面了,但將來項目上線以后,這個css也會放到一個專門的服務器上,比如cdn服務器,現在不考慮外網服務器,就放到自己項目內部即可。

3)根據模板示例, jquery.min.js 和 bootstrap.min.js 文件引入body標簽里面,所有js建議放到body底部區域:

 <body>
    <h1>你好,世界!</h1>

    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
同樣,地址改為我們自己的項目地址:
    <script src="/jquery/jquery.min.js"></script>
    <script src="/bootstrap/js/bootstrap.min.js"></script>

4)根據官網教程使用Bootstrap,比如 全局 CSS 樣式

 

4-1) 首先最上面概覽里面介紹,如果是HTML5,建議這樣寫:

<!DOCTYPE html>
<html lang="zh-CN">
  ...
</html>

4-2) 還有移動設備優先,排版與連接,和布局容器:

4-3) 如果想移動設備優先,也可以做如下設置:

<meta name="viewport" content="width=device-width, initial-scale=1"> 或者:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

4-4) 比如布局容器:我們選擇一個類選擇器.container,一個容器應用在goods.html中試驗。

.container 類用於固定寬度並支持響應式布局的容器。

<div class="container">
  ...
</div>
.container-fluid 類用於 100% 寬度,占據全部視口(viewport)的容器。

<div class="container-fluid">
  ...
</div>

4-5) 我們還想改一下表格的樣式,比如我們在 https://v3.bootcss.com/css/#tables 找到表格,如果想要這種表格,就在table標簽內加上如下屬性:

<table class="table">
  ...
</table>

4-6) 我們還想改一下button的樣式,比如我們在https://v3.bootcss.com/css/#buttons 找到按鈕,我們要這個首選項是藍色的這個:

<button type="button" class="btn btn-primary">(首選項)Primary</button>

4-7) 我們在goods.html中,body標簽內部也加上一個div,將非script腳本標簽的元素全部包在此div內部,然后div有一個屬性class="container",一個容器,表示把這些內容寫到一個容器里面,同時把表格table的樣式,和 添加商品 按鈕的樣式,代碼如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">

</head>
<body>
<div class="container">
 <h1>The Goods Page</h1>
 <button type="button" class="btn btn-primary" onclick="doLoadAddUI()">添加商品</button>
 <a th:href="@{/goods/doGoodsAddUI}">添加商品</a>
 <table class="table">
    <thead><!-- thead通常用於定義表格的title部分 -->
       <tr>
         <th>id</th> <!-- th用於定義當前列的標題 -->
         <th>name</th>
         <th>remark</th>
         <th>createdTime</th>
         <th colspan="2">operation</th>
       </tr>
    </thead>
    <tbody><!-- tbody中通常用於定義表格的表體部分:呈現具體業務數據的區域 -->
        <tr th:each="g:${list}">
          <td th:text="${g.id}">1</td>  
          <td th:text="${g.name}">MySQL</td>  
          <td th:text="${g.remark}">RDBMS</td>  
          <td th:text="${#dates.format(g.createdTime, 'yyyy/MM/dd HH:mm')}">2020/08/03 16:10</td> 
          <td><a th:href="@{/goods/doDeleteById/{id}(id=${g.id})}">delete</a></td> 
          <td><a th:href="@{/goods/doFindById/{id}(id=${g.id})}">update</a></td> 
        </tr>
    </tbody>
 </table>
</div>
 <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
 <script src="/jquery/jquery.min.js"></script>
 <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
 <script src="/bootstrap/js/bootstrap.min.js"></script>
 <!-- 建議:所有的js代碼寫到body的最底端 -->
 <script type="text/javascript">
      function doLoadAddUI(){
          //跳轉到url對象的地址
          location.href='/goods/doGoodsAddUI';
          //location.href='doGoodsAddUI';
      }
 </script>
</body>
</html>

4-8)代碼和效果圖對比之在引入Bootstrap樣式之前:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 <h1>The Goods Page</h1>
 <button type="button" onclick="doLoadAddUI()">添加商品</button>
 <a th:href="@{/goods/doGoodsAddUI}">添加商品</a>
 <table>
    <thead><!-- thead通常用於定義表格的title部分 -->
       <tr>
         <th>id</th> <!-- th用於定義當前列的標題 -->
         <th>name</th>
         <th>remark</th>
         <th>createdTime</th>
         <th colspan="2">operation</th>
       </tr>
    </thead>
    <tbody><!-- tbody中通常用於定義表格的表體部分:呈現具體業務數據的區域 -->
        <tr th:each="g:${list}">
          <td th:text="${g.id}">1</td>  
          <td th:text="${g.name}">MySQL</td>  
          <td th:text="${g.remark}">RDBMS</td>  
          <td th:text="${#dates.format(g.createdTime, 'yyyy/MM/dd HH:mm')}">2020/08/03 16:10</td> 
          <td><a th:href="@{/goods/doDeleteById/{id}(id=${g.id})}">delete</a></td> 
          <td><a th:href="@{/goods/doFindById/{id}(id=${g.id})}">update</a></td> 
        </tr>
    </tbody>
 </table>
 <!-- 建議:所有的js代碼寫到body的最底端 -->
 <script type="text/javascript">
      function doLoadAddUI(){
          //跳轉到url對象的地址
          location.href='/goods/doGoodsAddUI';
          //location.href='doGoodsAddUI';
      }
 </script>
</body>
</html>

效果圖:

4-9)效果圖對比之在引入Bootstrap樣式之后:

模擬hi現場互聯網項目的活動業務功能:

程序思路設計:

 

 調用流程設計:

服務端實現:

第一步:定義pojo對象(com.cy.pj.activity.pojo.Activity)

第二步:定義ActivityDao接口及方法

第三步:定義ActivityService接口及實現類

第四步:定義ActivityController對象及url映射

客戶端實現:

第一步:定義activity.html頁面

第二步:通過Thymeleaf模板引擎將活動數據呈現在頁面上。

程序代碼實現:

1)activity表結構對象建模:在com.cy.pj.activity.pojo包下,創建實體類Activity.java,用於存儲用戶的活動信息,這個活動信息有可能是從數據庫查詢出來的,也有可能是在做保存操作的時候,用來接收頁面端傳遞過來的數據。

2)對應數據庫表結構,構建Activity對象屬性,get/set及toString方法等:

3)在src/main/java/com/cy/activity/pojo包下,創建Activity.java,代碼實現:

package com.cy.pj.activity.pojo;

import java.util.Date;

/**
 * 
 * @author Administrator
 *
 */
public class Activity {

    private Long id;
    /** 活動標題 */
    private String title;
    /** 活動類型 */
    private String category;
    /** 活動開始時間 */
    private Date startTime;//java.util.Date
    /** 活動結束時間 */
    private Date endTime;
    /** 活動備注 */
    private String remark;
    /** 活動狀態(已啟動,已結束,...) */
    private Integer state;
    /** 創建用戶:一般是登錄用戶 */
    private String createdUser;
    /** 創建時間:用戶無需自己填寫,有底層系統生成 */
    private Date createdTime;
    
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getCategory() {
        return category;
    }
    public void setCategory(String catagory) {
        this.category = catagory;
    }
    public Date getStartTime() {
        return startTime;
    }
    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }
    public Date getEndTime() {
        return endTime;
    }
    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }
    public String getRemark() {
        return remark;
    }
    public void setRemark(String remark) {
        this.remark = remark;
    }
    public Integer getState() {
        return state;
    }
    public void setState(Integer state) {
        this.state = state;
    }
    public String getCreatedUser() {
        return createdUser;
    }
    public void setCreatedUser(String createdUser) {
        this.createdUser = createdUser;
    }
    public Date getCreatedTime() {
        return createdTime;
    }
    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime;
    }
    @Override
    public String toString() {
        return "Activity [id=" + id + ", title=" + title + ", category=" + category + ", startTime=" + startTime
                + ", endTime=" + endTime + ", remark=" + remark + ", state=" + state + ", createdUser=" + createdUser
                + ", createdTime=" + createdTime + "]";
    }
        
}

根據表結構設計:

第一個字段id是bigint(20),表示活動ID,一般地,我們在對象中bigint對應字段為Long類型屬性id,但我們非要定義成Integer也是可以的,沒有問題;

第二個字段title是varchar(100),活動標題定義為String類型;

第三個字段category是varchar(100),一般地,類別可以定義為枚舉類型,或者可以定義為字符串String,在頁面上給定幾個值去選擇;

第四、五個字段是startTime和endTime是datetime,表示可以接收年月日時分秒,我們可以定義為Date(java.util)類型,java.sql.Date是在從數據庫里面取數據時,做數據封裝的時候會用,而在java程序中使用的類,就是java.util.Date,否則的話,程序會報錯;

第六個字段remark是text,活動備注也可以定義為String類型;

第七個字段state是tinyint(4),數據庫里設置的是tinyint小整形,當然如果這里只有兩種狀態的話,還可以設置為布爾類型,活動狀態有效或無效,否則就需要設置為小整形,比如活動創建,已創建未啟動,活動已經啟動,活動已結束等。所以這里小整型在對象中對應的字段為Integer即可;

第八個字段createdUser是varchar(100),表示哪一個用戶創建的活動,一般就是指登錄用戶,對應字段類型為String;

第九個字段createdTime是datetime,表示創建時間,無需用戶自己填寫,由底層系統生成,對象中用java.util.Date

 一般地,我們從數據庫查詢出來的數據,要存儲到這個對象中,默認是先調set方法,如果是把數據寫入數據庫,寫在sql當中,是調用get方法來取值,所以生成set/get方法,一般地,為了輸出方便,還會重新toString方法。

4)根據程序設計,我們需要做查詢,把數據庫里的數據查詢出來,我們還需要一個Dao層里面,去實現一下這個過程,那就得有一個Dao對象,一個接口;按照我們的結構來講,它應該在src/main/java/com/cy/pj/activity/dao包下,創建一個接口ActivityDao.java:

說明:在這個ActivityDao當中,第一步,我們可能由MyBatis底層來給這個接口產生一個實現類,習慣地,在數據層的接口之上加一個@Mapper注解,然后在接口內部定義一個方法,其返回值是List<Activity>,方法名為findActivities();,引入java.util.List集合包,通過@Select注解寫一個查詢映射,@Select("select * from tb_activity order by createdTime desc"),此處簡寫以星號代表查詢所有(實際工作時不建議寫星號),最后記得寫文檔注釋,查詢所有活動信息,返回值就是活動信息。

注意:

* 在后續在查詢時,可能會做一些調整,不一定是直接返回一個List集合,可能還會做一個分頁的查詢等多種設計,也有人會在List集合里面放一個Map,List<Map<String,Object>>,如果是這種形式的話,那就是一行記錄一個Map對象,這時pojo都可以不寫了;我們現在這里用的是pojo對象來封裝,即一行記錄,

映射為內存中的一個pojo對象。

* 這里還需要注意的是,我們只寫了接口,那么沒有實現類,是誰負責與數據庫進行交互,當然這里其實是SqlSession,雖然我們沒有自己去用它,但在Mybatis底層的實現類里面,肯定是SqlSession與數據庫交互的,更具體一點,其實就是Connection,再具體一點就是JDBC API,即底層會通過JDBC API去實現服務器與數據庫的交互。

* 那誰負責將ResultSet結果集中的數據取出來存儲到Activity,我們說是MyBatis,再具體一點,那是MyBatis中哪一個對象去處理呢,就是ResultSetHandler,這是Mybatis里面的一個對象,我們數據庫里面是一行一行的記錄,那把這一行數據取出來的是誰呢,這肯定是通過SqlSession與數據庫進行會話的,取出來的結果可能要存儲到Activity對象,那誰去幫我存儲呢,就是ResultSetHandler,這屬於Mybatis的API,Ctrl+Shift+T打開ResultSetHandler,它是一個接口,里面有三個方法,其中List<E> handleResultSets(Statement stmt)是處理結果集的,首先這個方法返回的為什么是List集合啊?那注意我們這里的pojo傳的是什么,那么這個List<E>集合,指定的泛型就是什么;那方法的參數是Statement,這個Statement的作用是負責發送sql的,執行sql還是在數據庫端,Statement僅僅相當於是一個傳送器,把sql發送到數據庫端,假如是查詢sql,Statement會獲取一個ResultSet對象,引入結果集;那么handleResultSets方法的內部會做什么事情呢,就是把結果集中的數據取出來,取出來以后,最終存儲到一個List集合中,當然ResultSetHandler僅僅是一個接口,如果想找到接口的實現類,選中此接口名稱,Ctrl+T,就可以看到它的實現類為DefaultResultSetHandler,即結果集的處理。有的時候,我們從數據庫取數據了,取出來以后,那它怎么就存到對象里面了么,肯定是有人負責去把數據取出來存到對象,那我們就可以找到這個ResultSetHandler的實現類DefaultResultSetHandler,在這個實現類里面,Ctrl+O,找到那個List<Object> handleResultSets(Statement stmt)方法:

  //
  // HANDLE RESULT SETS
  //
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

其中,handleResultSet(rsw, resultMap, multipleResults, null);,就是處理結果集,這就是在處理結果集,取數據做映射,真正的結果映射就在這。

    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

可以找到handleResultSet方法,從而找到具體的那個結果集的映射:其中 handleRowValues 就是映射行,從這可以了解這個結果集的映射與存儲。

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

以上就是Mybatis處理結果集的源碼,所謂處理結果集,

handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping)

就是把結果ResultSetWrapper取出來,可能會存儲List<Object>這個List集合里面,就ok了。這就是為什么我們只寫了一個ActivityDao接口的方法List<Activity> findActivitys(),那寫完這個接口方法上面,我們定義了一個sql,其實系統底層肯定是要把這sql發送到我們的數據庫端,數據庫端執行這個sql,執行完以后,

可能會返回一個結果,那個結果就是一個ResultSet,然后誰從ResultSet里面把數據取出來,存儲到List<Activity>,這么一個List集合里面,就是ResultSetHandler,結果集處理器:

package com.cy.pj.activity.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;


import com.cy.pj.activity.pojo.Activity;

@Mapper
public interface ActivityDao {
     @Select("select * from tb_activity order by createdTime desc")
     List<Activity> findActivitys();
}

那數據層接口寫好以后,那下一步,我們可能會寫一個業務層接口ActivityService 和 它的實現類ActivityServiceImpl,以及它們分別所在的包com.cy.pj.activity.service和com.cy.pj.activity.service.impl:

先創建ActivityService接口,並在其中定義一個方法List<Activity> findActivitys();,這個方法是控制層與業務層連接的入口,即控制層通過接口調用業務層的這個方法,這個方法名與數據層接口ActivityDao中的那個方法是一樣,都是List<Activity> findActivitys();,在引包的時候,引入某個包下的類,Ctrl+Shift+O,

自動引入,ActivityService,這是業務層接口。

package com.cy.pj.activity.service;
import java.util.List;
import com.cy.pj.activity.pojo.Activity;
//引入包中的類:ctrl+shift+o

public interface ActivityService {
    List<Activity> findActivitys();
}

那這個ActivityService接口下面,我們還會寫上一個實現類,在com.cy.pj.activity.service.impl包下,創建ActivityServiceImpl類去實現這個ActivityService接口,然后這個實現類根據設計,是需要交給Spring管理的,所以類上用@Service業務層注解描述,這個類可能要依賴於數據層對象,

並通過數據層對象去執行我們的數去查詢,聲明一個private ActivityDao activityDao;,並通過Spring給它注入一個值,所以屬性上用@Autowired注解描述,其他的業務暫時先不寫,直接在findActivitys()方法內調用它的數據層對象,return activityDao.findActivitys();,就看它的結構,以后的代碼接口都基本如此形式,

后續的業務邏輯也都會寫在這里,比如緩存功能,日志功能等;然后在控制層直接調用我們的業務層方法來拿到一個結果。

package com.cy.pj.activity.service.impl;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.cy.pj.activity.dao.ActivityDao;
import com.cy.pj.activity.pojo.Activity;
import com.cy.pj.activity.service.ActivityService;

@Service
public class ActivityServiceImpl implements ActivityService {

    @Autowired
    private ActivityDao activityDao;
    
    @Override
    public List<Activity> findActivitys() {
         return  activityDao.findActivitys();
    }
}

下一步是控制層,在com.cy.pj.activity.controller包下,創建ActivityController,首先這ActivityController也要交給Spring管理,所以通過@Controller注解描述,並讓它耦合於業務層接口ActivityService,

然后通過注解@Autowired為它注入一個實現類,然后在其下再定義一個方法,public String doFindActivitys(),並希望其返回值為activity.html頁面,請求映射通過注解@RequestMapping("/activity/doFindActivitys")描述,接下來,List<Activity> list = activityService.findActivitys();,調用業務層方法findActivitys(),

返回一個List<Activity>集合,先看看能不能拿到數據,數據的顯示是拿到數據以后的操作,所以做一個輸出,list.forEach((item)->System.out.println(item));,集合點forEach方法,這是jdk8的新特性,item是集合中的一個數據,然后輸出這個item數據,forEach這就相當於通過for循環迭代這個List集合,其中,

-> 是一個箭頭函數,在java中其實它是運算符,是jdk8對for循環的簡化寫法。for(int i=0; i<list.size(); i++){System.out.println(list.get(i));},或者 for(Activity item:list){System.out.println(item);},再或者新的寫法,list.forEach(System.out::println);,這4中方式都是可以的,通過看源碼可以看到別人在用的一些新特性,

比如jdk8中lambda表達式箭頭函數,和jdk8中的雙冒號方法引用,以及增強for循環。代碼實現如下:

package com.cy.pj.activity.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.cy.pj.activity.pojo.Activity;
import com.cy.pj.activity.service.ActivityService;

@Controller
public class ActivityController {
     @Autowired
     private ActivityService activityService;
          
     /**查詢所有活動信息*/
     @RequestMapping("/activity/doFindActivitys")
     public String doFindActivitys(Model model) {
         List<Activity> list=activityService.findActivitys();
//         for(int i=0; i<list.size(); i++) {
//             System.out.println(list.get(i));
//         }
         
//         for(Activity item:list) {
//             System.out.println(item);
//         }
         
//         list.forEach((item)->System.out.println(item));//JDK8 lambda
         
         list.forEach(System.out::println);//JDK8 方法引用
         return "activity";
     }
}

最后在控制層的doFindActivitys是返回一個頁面,這個頁面還沒有,接下來把這個頁面先創建出來,在src/main/resources/templates/modules目錄下,創建一個activity.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1>The Activity Page</h1>
</body>
</html>

頁面也有了,然后啟動服務器,我們一般在Boot Dashboard這個位置,通過選擇項目右鍵(Re)start,去啟動服務器,當在這啟動服務的時候,在Console控制台這個位置,右上方有一個圈A,當點擊這個A的時候,控制台在服務器啟動時的輸出將會以方框問號為分隔符,重新點中取消重啟即可。

 訪問localhost:8080/activity/doFindActivitys,將出現如下頁面:

控制台輸出如下:

下一步,通過BootStrap和thymeleaf將數據呈現在activity.html的頁面上:

首先可以先把ActivityController這個類中的doFindActivitys方法中的打印測試語句先提取出來,不需要用了,Alt+Shift+M,提取到方法doPrint(list)中,通過這個方法封裝這段代碼打印一個集合,把集合里面的內容輸出一下,不需要打印,就注釋掉這個doPrint(list);即可:

 代碼如下:

package com.cy.pj.activity.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.cy.pj.activity.pojo.Activity;
import com.cy.pj.activity.service.ActivityService;

@Controller
public class ActivityController {
     @Autowired
     private ActivityService activityService;
          
     /**查詢所有活動信息*/
     @RequestMapping("/activity/doFindActivitys")
     public String doFindActivitys(Model model) {
         List<Activity> list=activityService.findActivitys();
         //doPrint(list);
         return "activity";
     }

    private void doPrint(List<Activity> list) {//選中代碼,快速提取方法,alt+shift+m
       //方法1:
//         for(int i=0; i<list.size(); i++) {
//             System.out.println(list.get(i));
//         }
           //方法2:
//         for(Activity item:list) {
//             System.out.println(item);
//         }
           //方法3:
//         list.forEach((item)->System.out.println(item));//JDK8 lambda
         //方法4:
         list.forEach(System.out::println);//JDK8 方法引用
    }
}

 那我們不是通過這個doPrint(list)方法把數據打印到控制台,我們要做的是把這個數據輸出到activity.html頁面上,下面我們就開始把activity.html的頁面結構實現完整。

那么要做的是在這個頁面上顯示活動信息,為了獲得這些活動數據,我們可以把ActivityController類的doFindActivitys方法里的,

         List<Activity> list=activityService.findActivitys();

這個地方獲取的List集合,把它存儲到一個Model對象中,因此為doFindActivitys添加一個Model參數,並通過這條語句,model.addAttribut("list", list);,將數據存到Model中,代碼如下:

     /**查詢所有活動信息*/
     @RequestMapping("/activity/doFindActivitys")
     public String doFindActivitys(Model model) {
         List<Activity> list=activityService.findActivitys();
         //doPrint(list);
       model.addAttribute("list", list); 
       return "activity";
   } 

那么在頁面上如何去呈現這一部分數據呢,通常body標簽內部,會分成幾個div容器里面,我們把h1標簽放到div里面,在h1下面再寫一個table標簽,在table里面,

我們寫上表格的標題部分(表頭),thead標簽,在標題里面有tr和td或者th,和td相比,th標簽的特點是標題內容會自動居中,當然,這個表格的標題部分,這塊可以動態生成,即服務端返回了幾列,在thead里面就創建幾個td或th,服務端的List集合有多少行,我們這邊的tr就動態的創建幾個等。

我們這里有幾列,即幾個th,是根據Activity實體類進行相應的設置的,比如有title標題,有分類Category,或者說類型,有開始時間StartTime,有結束時間EndTime,有State狀態,還有Remark備注,這個備注有的時候是不顯示的,備注是查看詳細信息時才顯示的,這里干脆也不顯示它了;至於Acitivity實例類中的創建用戶createdUser和創建時間createdTime,這里就不寫了,實際具體顯示什么數據會由業務需求規定。最后再給上一個操作吧,后面可能也需要有一些操作,比如叫Operation。然后表格中具體的內容數據,我們顯示在tbody里面,tbody中我們顯示是tr,在tr內部,我們一般情況下,用的是這個屬性去迭代我們的數據,th:each="${}",如果用thymeleaf的話,就直接<tr th:each="aty:${list}"> </tr>,在${list}內部寫上list,並指定一個變量aty(activity),接收這個服務端Model傳遞過來的數據,然后在tr內部,我們對應表頭寫上相應的td標簽,這個對應標題的td,我們寫上<td th:text="${aty.title}"></td>,接着將其他從服務端響應過來的數據也響應的獲取到,最后一個Operation操作所對應的td,我們寫上一個按鈕,關於開始時間StartTime和結束時間EndTime,一般的我們會按照自己的設計將其格式化,那這個格式呢,在thymeleaf里面,是在 https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html,在第19章的Dates這個位置,就可以找到日期格式:

根據參考格式, ${#dates.format(date, 'dd/MMM/yyyy HH:mm')},我們寫成

<td th:text="${#dates.format(aty.startTime, 'yyyy/MM/dd HH:mm')}"></td>

,這里就不顯示小時分鍾了,實際這個是根據業務需求而定的,代碼如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <div>
        <h1>The Activity Page</h1>
        <table>
            <thead>
                <tr>
                    <th>title</th>
                    <th>Category</th>
                    <th>StartTime</th>
                    <th>EndTime</th>
                    <th>State</th>
                    <th>Operation</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="aty:${list}">
                    <td th:text="${aty.title}"></td>
                    <td th:text="${aty.category}"></td>
                    <td th:text="${#dates.format(aty.startTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${#dates.format(aty.endTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${aty.state}"></td>
                    <td>Delete</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

把服務器啟動起來,訪問 http://localhost:8080/activity/doFindActivitys,訪問效果如下:

那如果想把這個樣式改的更加美觀一點,就可以根據前述的goods商品模塊,引入BootStrap前端框架對表格樣式改善,之前已經把BootStrap和jquery的靜態資源和腳本引入到這個activity活動模塊中,src/main/resources/static目錄下的jquery目錄和bootstrap目錄,有 jquery.min.js 和bootstrap相關的資源;

接下來就在activity.html頁面引入這些資源(根據goods.html),第一個在head標簽里引入bootstrap的css,第二個在body標簽的底部引入jquery和bootstrap。然后樣式根據goods.html對boostrap的引入,對於div來說可以用class="container",對於table這就class="table",首先是bootstrap對css的引用:

<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">

然后,bootstrap對js的引用:

 <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
 <script src="/jquery/jquery.min.js"></script>
 <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
 <script src="/bootstrap/js/bootstrap.min.js"></script>

 現在沒有關系,但后面會利用js來操作一些頁面上的對象時會用到,然后對於表格的最后一列Operation操作,我們可能會寫上一個button表示delete操作,然后在table之上,我們也可能會寫一個button表示添加活動操作,button的樣式,我們可以可以分別設置為紅色的,type="button" class="btn btn-danger",並且小一點的按鈕,type="button" class="btn btn-danger btn-sm"和 type="button" class="btn btn-primary":

 

 

代碼實現如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1>The Activity Page</h1>
        <button type="button" class="btn btn-primary">添加活動</button>
        <table class="table">
            <thead>
                <tr>
                    <th>title</th>
                    <th>Category</th>
                    <th>StartTime</th>
                    <th>EndTime</th>
                    <th>State</th>
                    <th>Operation</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="aty:${list}">
                    <td th:text="${aty.title}"></td>
                    <td th:text="${aty.category}"></td>
                    <td th:text="${#dates.format(aty.startTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${#dates.format(aty.endTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${aty.state==1?'有效':'無效'}"></td>
                    <td><button type="button" class="btn btn-danger btn-sm">delete</button></td>
                </tr>
            </tbody>
        </table>
    </div>
    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
    <script src="/jquery/jquery.min.js"></script>
    <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
    <script src="/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>

 訪問 http://localhost:8080/activity/doFindActivitys 效果圖如下:

 上面State,是一個數字1,數據庫里保存的是1和0,但是這里想讓它顯示的是狀態1-有效 或 0-無效,所以可以把這一列改為條件表達式,或者說三目運算符,在thymeleaf中同樣支持:<td th:text="${aty.state==1?'有效':'無效'}"></td>,這個狀態有效無效,指的是超時了,超過了活動結束時間,我們就需要把這個狀態給改了,這個怎么改呢,可能要寫一個任務調度,任務調度指的是,比如說我們在創建這條活動記錄的時候,我們就要啟動一個線程,這個線程就要對這個活動進行監控,一旦達到了比如到達活動結束時間以后,我們就去修改這個數據庫中的狀態state,把這個state的值改成 0-無效。這個不是手動去改,而是隨着我們這個時間的滾動,它是動態更新的,當數據庫里面的這個數據改了以后,在頁面刷新時也會隨之改動為有效或無效,這種情況下就是典型的任務調度了。這個任務調度可以使用線程池,也可以使用java當中的Timer類,就類似於我們在網上買了一個商品,下了一個訂單,但這個訂單30分鍾沒有付錢,或者20分鍾沒有付錢,訂單自動取消,這個自動取消不是人工去取消的,而是我們這個訂單到了一定的時間點以后,還沒有被支付,它就取消了。那我們這里面創建的這種活動,后面做添加活動時也是一樣,當在點擊添加活動時,把活動信息寫入數據庫了,那就要考慮一個問題,什么時候這個數據庫里面活動信息的狀態會改成無效呢,不需要自己手動去改,那就得開啟一個任務調度。

那這種情況下該怎么去啟動這個任務調度,實現這個業務功能呢,如果實現了這個過程,就會意識到那個電商里面的惡意訂單就是下了訂單不付錢,或者在12306上訂個票不支付,那同樣,這個訂單到點以后,不支付會自動取消,那么這里面的活動的狀態,我們計划也這么玩。那么到這,我們把服務端的數據已經存儲到一定的作用域,在客戶端進行呈現這個過程也完成了,然后又通過引入bootstrap.min.css,更改了樣式,其次在body區底部又引入了jquery和bootstrap.min.js,即我們查詢,頁面的呈現,並且基於bootstrap去修改我們頁面表格的樣式,就都做完了。

 那下面我們要做的是添加活動這個功能操作,就是說我要把頁面上的新增一個活動數據,要寫到數據庫里面,而且我們不打算再去寫一個頁面,這個可以在我們當前頁面中去寫,在當前頁面中,要做的業務需求是,打開Bootstrap官網的JavaScript插件:

這里有一個模態框,什么叫模態框,簡單來說就是彈出一個窗口,還有demo示例,根據這個demo示例。那我們希望在我們這個activity.html這個頁面中,點擊左上方的添加活動按鈕時,彈出一個窗口,在窗口里面輸出我們的活動信息,輸出完以后關閉,下面的活動頁面自動更新,就類似於在Hi現場那個頁面上點擊登錄或注冊時,彈出了一個框,這里也做這么一個業務。其實這種模態框的應用有很多很多場景,就類似於我們在sts中Ctrl+N,彈出一個窗口,也是模塊框的一個場景,那我們如何去實現這個場景呢,其實對於Bootstrap來講,它給出了這樣一種形式,一個模態框的代碼,它有標題部分,<div class="modal-header">,中間部分,<div class="modal-body">,最后一部分,<div class="modal-footer">,還有按鈕,在最上方它也有一個按鈕去觸發模態框,那我們就可以替代我們自己寫的按鈕,<button type="button" class="btn btn-primary">添加活動</button>,因為這些bootstrap已經幫我們寫好了,那我們Ctrl+CV即可:

 Bootstrap模態框示例demo:

<!-- Button trigger modal -->
<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
  Launch demo modal
</button>

<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="myModalLabel">Modal title</h4>
      </div>
      <div class="modal-body">
        ...
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

我們將最上面按鈕內部的 Launch demo modal 的名稱改為 創建活動,那在當前的應用當中,點擊創建活動按鈕,它其實是怎么顯示的這個模態框呢,這個模態框默認就有,只是它狀態是隱藏狀態,當我們在點擊創建活動按鈕時,這個按鈕的屬性上,它底層設置了這么一個開關,data-toggle="modal",這個開關的作用是什么呢,讓我們這模態框,data-target="#myModal",給顯示出來,這個按鈕里的屬性上的data-target="#myModal",這個data-target等於的id,它會和模態框里,即

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">

觸發模態框按鈕的屬性上 data-target="#myModal",會和這個模態框的div的屬性里的id相同,data-toggle="modal" data-target="#myModal",就是讓modal(這里的modal應該指class里的) 的id為myModal的模態框顯示出來,模態框即是一個隱藏在網頁中的窗口,那在這個窗口里面,標題部分,中間部分 和 底部的按鈕,我們可以替換為自己的功能的內容,比如第一個標題部分,我們改為創建活動,<h4 class="modal-title" id="myModalLabel">創建活動</h4>;中間部分的div內部是我們要寫的表單,<div class="modal-body"> ...</div>我們的表單可以放到這個div內部,所以我們要把我們添加活動的表單寫到這個位置,那我們在Bootstrap官網 全局CSS樣式 找到表單,選擇其中水平排列的表單:

 注:單獨的表單長度都設置為100%,如下:

demo示例代碼:

<form class="form-horizontal">
  <div class="form-group">
    <label for="inputEmail3" class="col-sm-2 control-label">Email</label>
    <div class="col-sm-10">
      <input type="email" class="form-control" id="inputEmail3" placeholder="Email">
    </div>
  </div>
  <div class="form-group">
    <label for="inputPassword3" class="col-sm-2 control-label">Password</label>
    <div class="col-sm-10">
      <input type="password" class="form-control" id="inputPassword3" placeholder="Password">
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
      <div class="checkbox">
        <label>
          <input type="checkbox"> Remember me
        </label>
      </div>
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
      <button type="submit" class="btn btn-default">Sign in</button>
    </div>
  </div>
</form>

將以上代碼放到模態框的body部分,但是其中的Remeber me記住我,和底下的button,Sign in也不需要,所以把它們刪掉,因為模態框里面有按鈕,所以按鈕也不需要,activity.html頁面代碼如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1>The Activity Page</h1>
        <!-- Button trigger modal -->
        <button type="button" class="btn btn-primary btn-lg"
            data-toggle="modal" data-target="#myModal">創建活動</button>

        <!-- Modal(模態框) -->
        <div class="modal fade" id="myModal" tabindex="-1" role="dialog"
            aria-labelledby="myModalLabel">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <!-- 標題部分 -->
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal"
                            aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                        <h4 class="modal-title" id="myModalLabel">創建活動</h4>
                    </div>
                    <!-- body部分 -->
                    <div class="modal-body">
                        <form class="form-horizontal">
                            <div class="form-group">
                                <label for="inputEmail3" class="col-sm-2 control-label">Email</label>
                                <div class="col-sm-10">
                                    <input type="email" class="form-control" id="inputEmail3"
                                        placeholder="Email">
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="inputPassword3" class="col-sm-2 control-label">Password</label>
                                <div class="col-sm-10">
                                    <input type="password" class="form-control" id="inputPassword3"
                                        placeholder="Password">
                                </div>
                            </div>
                        </form>
                    </div>
                    <!-- 按鈕部分 -->
                    <div class="modal-footer">
                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                        <button type="button" class="btn btn-primary">Save
                            changes</button>
                    </div>
                </div>
            </div>
        </div>
        <table class="table">
            <thead>
                <tr>
                    <th>title</th>
                    <th>Category</th>
                    <th>StartTime</th>
                    <th>EndTime</th>
                    <th>State</th>
                    <th>Operation</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="aty:${list}">
                    <td th:text="${aty.title}"></td>
                    <td th:text="${aty.category}"></td>
                    <td th:text="${#dates.format(aty.startTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${#dates.format(aty.endTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${aty.state==1?'有效':'無效'}"></td>
                    <td><button type="button" class="btn btn-danger btn-sm">delete</button></td>
                </tr>
            </tbody>
        </table>
    </div>
    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
    <script src="/jquery/jquery.min.js"></script>
    <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
    <script src="/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>

頁面效果圖如下:

那接下來,就可以根據活動表中的數據,去構建這個創建活動的模態框頁面,另外,至於這個模態框到底拷貝到哪里,我們現在放到了table之上,那把模態框放到table之下也無所謂,甚至放到table所在div的外面也是可以的。下面我們把模態框的body部分也改一下,

                    <!-- body部分 -->
                    <div class="modal-body">
                        <form class="form-horizontal">
                            <div class="form-group">
                                <label for="inputEmail3" class="col-sm-2 control-label">Email</label>
                                <div class="col-sm-10">
                                    <input type="email" class="form-control" id="inputEmail3"
                                        placeholder="Email">
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="inputPassword3" class="col-sm-2 control-label">Password</label>
                                <div class="col-sm-10">
                                    <input type="password" class="form-control" id="inputPassword3"
                                        placeholder="Password">
                                </div>
                            </div>
                        </form>
                    </div>

其中,<label for="inputEmail3" class="col-sm-2 control-label">Email</label>,這里我們就不寫Email了,改為title;然后,<input type="email" class="form-control" id="inputEmail3" placeholder="Email">,一個title就是一個普通的文本,類型改為text即可,它的id呢,改為titleId,隨之,label的for屬性也要改為titleId,那為什么label的for屬性要對應下面input的id屬性呢,作用就是無論我們點擊這個title的label標簽時,光標停在什么位置,這里的作用就是點擊這個title的label標簽時,會定位或選中id為titleId的input表單控件元素;那我們要把表單提交的話,還需要在這個id為titleId的input里面定義一個name屬性,比如name="title",這個name屬性的作用,可以把這個name為title所對應的信息,可能要提交到我們的服務端,服務器端是拿這個name的值(這里為title)作為參數為依據取值的,那title對應的值就是要寫到數據庫里面的那個值:

后面這個 <div class="form-group">,可能是一個類型,比如說有一個分類categoryId,下面的不想用input了啊,想改成下拉選項<select id="categoryId" class="form-control">,它里面有一些選項<option>,比如教育培訓,企業活動,交友活動等,那這些option后面的值是什么,即寫到數據庫時會有一些value,那這些value就是在option里面取定義的內容,value屬性是寫到數據庫的內容,option標簽體中的內容是顯示出來的內容,那select里面可能還有一個name="category",用一個選擇框實現這個控件,代碼如下:

                    <!-- body部分 -->
                    <div class="modal-body">
                        <form class="form-horizontal">
                            <div class="form-group">
                                <label for="titleId" class="col-sm-2 control-label">title</label>
                                <div class="col-sm-10">
                                    <input type="text" class="form-control" name="title" id="titleId"
                                        placeholder="Email">
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="categoryId" class="col-sm-2 control-label">類型</label>
                                <div class="col-sm-10">
                                      <select id="categoryId" name="category" class="form-control">
                                          <option value="教育培訓">教育培訓</option>
                                          <option value="企業活動">企業活動</option>
                                          <option value="交友活動">交友活動</option>
                                      </select>
                                </div>
                            </div>
                        </form>
                    </div>

那已經有了title和category,那我還需要有一個時間,創建時間和結束時間,即有效時長,那我想把兩個日期寫在一起,用如下的Bootstrap樣式:

demo示例代碼:

<form class="form-inline">
  <div class="form-group">
    <label class="sr-only" for="exampleInputEmail3">Email address</label>
    <input type="email" class="form-control" id="exampleInputEmail3" placeholder="Email">
  </div>
  <div class="form-group">
    <label class="sr-only" for="exampleInputPassword3">Password</label>
    <input type="password" class="form-control" id="exampleInputPassword3" placeholder="Password">
  </div>
  <div class="checkbox">
    <label>
      <input type="checkbox"> Remember me
    </label>
  </div>
  <button type="submit" class="btn btn-default">Sign in</button>
</form>

將其添加到我們的activity.html頁面中,只取前兩個div,將示例中的form,改為div,即這個div里面設置了一個class="form-inline",代碼效果如下:

                            <div class="form-inline">
                              <div class="form-group">
                                <label class="sr-only" for="exampleInputEmail3">Email address</label>
                                <input type="email" class="form-control" id="exampleInputEmail3" placeholder="Email">
                              </div>
                              <div class="form-group">
                                <label class="sr-only" for="exampleInputPassword3">Password</label>
                                <input type="password" class="form-control" id="exampleInputPassword3" placeholder="Password">
                              </div>
                            </div>

 發現前面還應該有一個label標簽更好,代碼修改及效果圖如下:

                            <div class="form-inline">
                                <label for="categoryId" class="col-sm-2 control-label">Category</label>                            
                                <input type="email" class="form-control" id="exampleInputEmail3" placeholder="Email">
                                <input type="password" class="form-control" id="exampleInputPassword3" placeholder="Password">
                            </div>

 發現效果並不好看,還不如寫成兩行的好,那復制第一個title的div進行相應的修改:

                            <div class="form-group">
                                <label for="startTimeId" class="col-sm-2 control-label">StartTime</label>
                                <div class="col-sm-10">
                                    <input type="text" class="form-control" name="startTime" id="startTimeId"
                                        placeholder="start time">
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="endTimeId" class="col-sm-2 control-label">EndTime</label>
                                <div class="col-sm-10">
                                    <input type="text" class="form-control" name="endTime" id="endTimeId"
                                        placeholder="end time">
                                </div>
                            </div>

最后還有一個textarea,可以選擇Bootstrap中的樣式,我們可以仿照title標簽,修改為textarea,並設置其行數rows="5":

 demo示例代碼:<textareaclass="form-control"rows="3"></textarea>

代碼如下:

                            <div class="form-group">
                                <label for="remarkId" class="col-sm-2 control-label">Remark</label>
                                <div class="col-sm-10">
                                    <textarea type="text" class="form-control" rows="5" name="remark" id="remarkId"                                        placeholder="remark">
                                    </textarea>
                                </div>
                            </div>

最終效果如下:

那下一步,我們模態框點擊提交時是一個按鈕button,但是這個按鈕沒有寫到表單里面,當我們點擊這個Save按鈕的時候,我們可能要提交表單,把表單里面的數據寫到數據庫里面,我們並沒有用form表單里面的button type="submit"的形式的按鈕,而是用的form外面的一個按鈕button,那想把我們的表單的數據寫到我們的數據庫里面,應該如何提交,如何去實現呢,那首先得找到這個表單,可以通過表單的名字去找到這個表單,現在我們的表單還沒有名字,那就給它一個名字或者是起一個id,然后給form外部的button上加一個單擊事件onclick="doSaveObject()",把數據保存一下,我們寫一個方法,那這時我們點擊這個button的時候,就會有doSaveObject()的一個事件處理函數,那么在這個事件處理函數里面,想辦法把表單提交給服務器端即可。另外,在這個form表單的屬性上,我們可能還要寫上一個action,要跳轉到一個地址,可以用thymeleaf模板的寫法,th:action,如果沒有用thymeleaf標簽的話,直接寫那個地址也是可以的,那我們用thymeleaf寫法,比如我們想提交的地址是/activity/doSaveActivity,然后表單提交方式method="post",即

那這時在服務器端要做一個save操作,在控制層ActivityController類中,添加這樣一個方法doSaveActivity,並讓它返回到activity.html頁面,那這里先不考慮后台服務器保存刷新的問題,在這方法里首先做一個輸出,測試在客戶端提交的數據在服務器端是否可以收到,如果拿到數據,就可以把這些數據寫入數據庫里面了,那服務端的這個地址有了,接下來就可以去處理客戶端,在activity.html頁面里面如何提交表單中的數據,

     @RequestMapping("/activity/doSaveActivity")

     public String doSaveActivity(Activity activity) {

         System.out.println("activity="+activity);//檢查客戶端提交的數據

       //... ...

         return "activity";

     }

在activity.html中模態框中的form表單數據,我沒有用submit的表單控件,就是用了一個普通的button,這時如果想提交這個表單,就需要在這個button上定義一個單擊事件函數,那現在就寫下這個函數doSaveObject(),我們把函數寫在body體里面的最底部,首先在函數里面想取到模態框中的那個form,我們可以在瀏覽器的F12控制台測試,輸出一下如何獲取到那個form,可以發現通過$(form)的方式,$(form)這個是jquery中基於標簽獲取對象的一種方式,是可以拿到的,確實是個對象,因為頁面中現在只有一個form表單,拿到的對象里面也有submit這樣的函數,因為button沒有在form表單的內部,所以這里必須通過定義函數事件方式提交表單,否則如果只是一個普通的button是不會提交表單的:

客戶端activity.html的doSaveObject函數:

    <script type="text/javascript">
       function doSaveObject(){
         //表單校驗(可考慮使用正則表達式)
         //提交表單
         //$(form)基於標簽名(例如這里的標簽名form)稱獲取表單對象
         //submit為jquery中的一個對象函數,通過此函數可以提交表單.
          $("form").submit();//提交表單
       }
    </script>    

那下面我們試試,這樣取到的對象能否提交表單給服務器,那首先在項目的ActivityController中的doSaveActivity方法的System.out.println("activity="+activity)這,加一個斷點,然后(Re)debug Ctrl+Alt+Shift+B,D 啟動,加斷點的目的就想看看提交時,客戶端的數據有沒有提交到服務器端,服務器拿到沒有:

訪問 http://localhost:8080/activity/doFindActivitys 點擊創建活動按鈕,填上數據,比如創建活動,教育培訓,時間,這個開始和結束時間,默認必須以yyyy/mm/dd的格式來寫,服務器才能直接保存到數據庫中,而且將來這兩個時間肯定要校驗的,不能說結束時間還小於開始時間,備注就寫互動答疑,然后,點按鈕Save Changes:

 可以看到斷點在System.out.println("activity="+activity);這里,是接收到了客戶端提交的數據的,那接下來,把它們寫到數據庫中就完成了。此時發現瀏覽器端返回了一個空頁面,因為控制層里直接返回了頁面,並沒有刷新頁面的數據,服務端還沒有寫那個把數據寫到數據庫,再查詢,再回到這個頁面的過程:

 那如果想回到那個查詢數據的操作,可以return "redirect:/activity/doFindActivitys";,然后把那個斷點取消:

客戶端頁面,activity.html代碼如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1>The Activity Page</h1>
        <!-- Button trigger modal -->
        <button type="button" class="btn btn-primary btn-lg"
            data-toggle="modal" data-target="#myModal">創建活動</button>

        <!-- Modal(模態框) -->
        <div class="modal fade" id="myModal" tabindex="-1" role="dialog"
            aria-labelledby="myModalLabel">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <!-- 標題部分 -->
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal"
                            aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                        <h4 class="modal-title" id="myModalLabel">創建活動</h4>
                    </div>
                    <!-- body部分 -->
                    <div class="modal-body">
                        <form class="form-horizontal" th:action="@{/activity/doSaveActivity}" method="post">
                            <div class="form-group">
                                <label for="titleId" class="col-sm-2 control-label">title</label>
                                <div class="col-sm-10">
                                    <input type="text" class="form-control" name="title" id="titleId"
                                        placeholder="Email">
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="categoryId" class="col-sm-2 control-label">類型</label>
                                <div class="col-sm-10">
                                      <select id="categoryId" name="category" class="form-control">
                                          <option value="教育培訓">教育培訓</option>
                                          <option value="企業活動">企業活動</option>
                                          <option value="交友活動">交友活動</option>
                                      </select>
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="startTimeId" class="col-sm-2 control-label">StartTime</label>
                                <div class="col-sm-10">
                                    <input type="text" class="form-control" name="startTime" id="startTimeId"
                                        placeholder="start time">
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="endTimeId" class="col-sm-2 control-label">EndTime</label>
                                <div class="col-sm-10">
                                    <input type="text" class="form-control" name="endTime" id="endTimeId"
                                        placeholder="end time">
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="remarkId" class="col-sm-2 control-label">Remark</label>
                                <div class="col-sm-10">
                                    <textarea type="text" class="form-control" rows="5" name="remark" id="remarkId"                                        placeholder="remark">
                                    </textarea>
                                </div>
                            </div>                            
                        </form>
                    </div>
                    <!-- 按鈕部分 -->
                    <div class="modal-footer">
                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                        <button type="button" class="btn btn-primary" onclick="doSaveObject()">Save changes</button>
                    </div>
                </div>
            </div>
        </div>
        <table class="table">
            <thead>
                <tr>
                    <th>title</th>
                    <th>Category</th>
                    <th>StartTime</th>
                    <th>EndTime</th>
                    <th>State</th>
                    <th>Operation</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="aty:${list}">
                    <td th:text="${aty.title}"></td>
                    <td th:text="${aty.category}"></td>
                    <td th:text="${#dates.format(aty.startTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${#dates.format(aty.endTime, 'yyyy/MM/dd HH:mm')}"></td>
                    <td th:text="${aty.state==1?'有效':'無效'}"></td>
                    <td><button type="button" class="btn btn-danger btn-sm">delete</button></td>
                </tr>
            </tbody>
        </table>
    </div>
    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
    <script src="/jquery/jquery.min.js"></script>
    <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
    <script src="/bootstrap/js/bootstrap.min.js"></script>
    <script type="text/javascript">
       function doSaveObject(){
         //表單校驗(可考慮使用正則表達式)
         //提交表單
         //$(form)基於標簽名(例如這里的標簽名form)稱獲取表單對象
         //submit為jquery中的一個對象函數,通過此函數可以提交表單.
          $("form").submit();//提交表單
       }
    </script>    
</body>
</html>

到此為止,那創建活動唯一剩下的過程,就是把數據寫到數據就可以了,服務端我們一般是倒序來寫,先寫Dao層,然后Service層,最后寫Controller層,那首先來到ActivityDao這個接口,定義一個insertObject方法,然后給這個方法傳入一個Activity對象,返回值為int,即int insertObject(Activity activity);,

package com.cy.pj.activity.dao;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import com.cy.pj.activity.pojo.Activity;

@Mapper
public interface ActivityDao {
    
     int insertObject(Activity activity);
     /**
      *   查詢所有活動信息,一行記錄映射為內存中的一個Activity對象
      *  1)誰負責與數據庫交互?(SqlSession,再具體一點就是JDBC API)
      *  2)誰負責將ResultSet結果集合中的數據取出來存儲到Activity?(MyBatis,再具體一點就是ResultSetHandler)
      */
     @Select("select * from tb_activity order by createdTime desc")
     List<Activity> findActivitys();
}

然后我們把這個Sql語句寫到Mapper.xml映射文件里面去,我們從官網或其他的項目中拷貝一個Mapper.xml文件,放到src/main/resources/mapper/activity目錄下,命名為ActivityMapper.xml,命名空間namespace="com.cy.pj.activity.dao.ActivityDao",insert標簽上的id對應ActivityDao接口中的方法名,sql語句對應如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  <mapper namespace="com.cy.pj.activity.dao.ActivityDao">
  
     <insert id="insertObject">
         insert into tb_activity
         (title,category,startTime,endTime,remark,state,createdUser,createdTime) 
         values 
         (#{title},#{category},#{startTime},#{endTime},#{remark},#{state},#{createdUser},now())
     </insert>
     
  </mapper>

 這是Dao層的代碼實現,接下來是業務層Service,找到ActivityService接口,在com.cy.pj.activity.service包下。我們在這個ActivityService接口里第一個方法,int saveActivity(Activity entity);

package com.cy.pj.activity.service;
import java.util.List;
import com.cy.pj.activity.pojo.Activity;
//引入包中的類:ctrl+shift+o

public interface ActivityService {
    
    int saveActivity(Activity entity);
    List<Activity> findActivitys();
}

然后在service實現類里,去實現這個saveActivity(Activity entity)方法:在將要創建的活動信息保存的到數據庫,在把活動信息寫到數據庫里之后,我們還要做些什么,不是定時銷毀,我們說的是將來我們這里要開啟任務調度,比如說開啟任務或者活動倒計時,這就類似於下訂單,時間到了以后,不需要人手動,

去把這個活動禁用,也就是活動到了結束時間應該將其狀態修改為禁用0-狀態,即無效狀態,而開啟一個線程是公用一個tomcat線程,還是再開啟一個線程,那一定是開啟另一個線程,這就是業務操作,那如何實現這個業務功能,可以基於java官方的有兩種方案,第一個利用Timer類實現;

第二個利用ScheduleExecutorService,它是一個線程池中的任務調度;第三種方案,借助第三方的任務調度框架Quartz,任務調度框架的作用就是定時鬧鍾功能;那我們這里的活動業務,活動時間到了以后,應該再去調用dao的方法,將活動狀態由,1-有效,改為,0-禁用;

下面我們以Timer的方式為例,它是最簡單的實現方案,雖然存在很多弊端,並不理想,但是至少我們寫會了這種形式,是能解決問題的,先不去探究它的好與不好,至少我們是可以去解決的,比如類似的定時關機,定時任務取消等需求,都是可以用Timer去實現的,我們先new一個Timer出來,如下圖所示:

 通過Timer可以點出一些它里面的方法,實現任務調度功能的這個Timer里面能夠接收這個時間的,一個是毫秒數,還有一個Date,還有firstTime,還有執行多少多少次,很多反復執行的,方法如下(具體細節見jdk源碼):

        public void schedule(TimerTask task, long delay) {
            if (delay < 0)
                throw new IllegalArgumentException("Negative delay.");
            sched(task, System.currentTimeMillis()+delay, 0);
        }

        public void schedule(TimerTask task, Date time) {
            sched(task, time.getTime(), 0);
        }

        public void schedule(TimerTask task, long delay, long period) {
            if (delay < 0)
                throw new IllegalArgumentException("Negative delay.");
            if (period <= 0)
                throw new IllegalArgumentException("Non-positive period.");
            sched(task, System.currentTimeMillis()+delay, -period);
        }

        public void schedule(TimerTask task, Date firstTime, long period) {
            if (period <= 0)
                throw new IllegalArgumentException("Non-positive period.");
            sched(task, firstTime.getTime(), -period);
        }

 我們這里寫的還是比較簡單的,

 public void schedule(TimerTask task, Date time){... }

這個函數第一個參數,就是這里面的任務是TimerTask,我們直接在參數上new一個TimerTask(){},即以匿名內部類的方式執行任務,這個TimerTasker為任務,TimerTasker對象里有一個run方法,就是開始執行任務;那第二個參數是Date類型的時間time,就是到此時間以后去執行TimerTask任務,entity.getEndTime(),獲取活動結束時間,到此時間開始執行任務調度函數TimerTask,即按這個指定時間執行任務。這是構建一個Timer對象,這個Timer對象可以負責去執行一些任務,此對象通過內置一個線程和一個任務隊列,其中線程是負責執行啟動run方法執行我們的任務,而任務隊列的作用是,只有一個線程,如果有多個任務,即多次調用timer.schedule(task,time)函數,一個線程,多個任務,不可能同時執行,那么Timer對象就會把這些任務存儲到一個容器里面,一個隊列里面,先來的任務先執行,它是按照這么一種規則設計的對象。它的使用分為兩步:

第一步:構建Timer對象;

第二步:啟動線程執行任務,其中任務的類型是TimerTask類型,當我們這個Timer對象中內置的線程獲得了CPU以后,Timer對象就會自動調用這個任務對象TimerTask的run方法,然后,在指定的一個時間去執行這個任務。

        //開啟活動倒計時(活動到了結束時間應該將其狀態修改為0)
        //方案:(自己嘗試)
        //1)Java 官方:
        //1.1)Timer
        //1.2)ScheduledExecutorService
        //2)借助第三方的任務調度框架(任務調度框架,quartz)
        //方案1:Timer應用
        //1.1構建Timer對象
        Timer timer=new Timer();//此對象可以負責去執行一些任務(這個對象內置一個線程和一個任務隊列)
        //1.2啟動線程執行任務
        timer.schedule(new TimerTask() {//TimerTask為任務
            @Override
            public void run() {//一旦調用此任務的線程獲得了CPU就會執行這個任務的run方法
                System.out.println("updateState.threadName="+Thread.currentThread().getName());
                System.out.println("執行任務...");
                //activityDao.updateState(entity.getId());
            }
        }, entity.getEndTime());//按指定時間執行任務.

重啟下服務,創建一個活動AAAA,教育培訓,結束時間到10:35,馬上就10:35了,等一下看看是否會執行,這里的時間取得的是當前所在服務器的系統時間:

查看控制台,是否在指定結束時間,輸出執行任務,可以發現是可行的:

這就是按指定時間去執行任務,那我們就可以在TimerTask的run方法內部去修改活動的狀態信息了,要修改狀態,我們需要在ActivityDao中在定義一個方法updateState,這個方法的入參應該有個id,根據id去查詢此活動的記錄,並把它的這個狀態修改為 0-禁用 即可:

     @Update("update tb_activity set state=0 where id=#{id}")
     int updateState(Long id);

當然,此方法也可以傳兩個值,一個id,一個狀態值state,可以去修改基於此id的活動記錄的狀態為 0-禁用 或者 1-啟用。

數據層的Dao定義好方法以后,接下我們就可以在業務層的實現類ActivityServiceImpl.java中,直接調用這個ActivityDao中的updateState方法(這個方法沒有定義在ActivityService接口中),並為之傳入我們所要保存的對象的id,那這里有一個問題,既然是基於我們所正在創建的活動的id去修改狀態,那首先我們需要拿到這個id,而頁面上不可能提交id過來,因為這個活動是第一次創建的這么一條記錄,如果想拿到這條id,首先是需要把此記錄寫到數據庫以后,才能取到此id,那如何取到此id,這里有這樣的一種機制:

打開ActivityMapper.xml映射文件,我們保存活動的insert的sql語句就寫在這個文件中,我們想獲取這條寫入到數據庫表中的這個記錄的id值,因為這個id是自增長的,如果id是自增長的,那么它在mapper映射文件中的insert標簽里,有一個useGeneratedKeys屬性,將其設置為true,就表示我要獲取寫入到數據庫表中的主鍵值;然后把這個主鍵值再賦值給此sql語句對應的數據層ActivityDao接口中,與之關聯的方法參數對象中的另一個屬性,具體是這個對象的哪一個屬性,則由keyProperty來指定;那具體是方法中的哪個參數對象,可以由parameterType來指定,如果方法中只有一個參數對象,則parameterType通常是可以省略的,代碼如下:

1)ActivityMapper.xml映射文件對應sql:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  <mapper namespace="com.cy.pj.activity.dao.ActivityDao">
  
     <insert id="insertObject"
             parameterType="com.cy.pj.activity.pojo.Activity"
             useGeneratedKeys="true"
             keyProperty="id">
         insert into tb_activity
         (title,category,startTime,endTime,remark,state,createdUser,createdTime) 
         values 
         (#{title},#{category},#{startTime},#{endTime},#{remark},#{state},#{createdUser},now())
     </insert>
     
  </mapper>

2)數據層Dao 接口ActivityDao.java中對應方法:

package com.cy.pj.activity.dao;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import com.cy.pj.activity.pojo.Activity;

@Mapper
public interface ActivityDao {
     //...
     int insertObject(Activity activity);
     //...
}

useGeneratedKeys:表示使用insert操作中生成的自增主鍵值(這里必須是自增主鍵);

keyProperty:表示將獲得的自增主鍵值賦值給參數對象的指定屬性(這里是id屬性),至於具體賦值給哪一個屬性,需要根據業務需求而定;

注:這個keyProperty所指定的屬性,一定是parameterType所指定的參數對象中的一個屬性(我們這里指定的是com.cy.pj.activity.pojo.Activity),SpringMVC底層可能調用的是這個參數對象中(比如Activity對象)的setId方法,獲取到的自增主鍵的值,賦值給parameterType所指定的對象,如果此對象中沒有相應set方法,也可能是直接給屬性賦值,所以就直接可以說給parameterType所指定的參數對象的被keyProperty所指定的屬性賦值,這里是給Activity對象中的id屬性賦值。因為這個參數對象是從客戶端向服務端傳數據時,由SpringMVC幫我們創建的,這是SpringMVC的一個規則,SpringMVC創建完Activity對象以后,這個對象里面並沒有id值,我們從客戶端向服務端提交數據,我們並沒有提交id值,但是在這條活動數據創建完以后,我想立即基於id去修改這條記錄,比如在我們的ActivityServiceImpl當中,基於id去修改這條記錄,禁用它的活動狀態,但從頁面提交的數據,在服務器做了封裝成Activity對象,這個對象中並沒有id,但是我想拿到insert操作,寫入到數據庫表中這條記錄的id值,就可以采用這樣的一種策略,返回自動主鍵功能。即在SpringMVC的Mapper映射文件中的insert標簽上,首先通過useGeneratedKeys告訴SpringMVC我們要用一下這個自增主鍵的值,然后通過keyProperty告訴SpringMVC,我們還要在parameterType所指定的參數對象中的某一個屬性上去用一下這個獲取到的主鍵值,從而當我們的insert操作執行完畢,這個由parameterType所指定的這個由客戶端傳遞過來的數據,在服務器做了封裝的參數對象里面的id屬性就有值了,因此我們就可以利用這個對象,在業務層基於它的id對其所對應的記錄進行修改,

activityDao.updateState(entity.getId());

這樣就實現了在創建活動的同時,獲取活動記錄的自增主鍵id值,從而在活動結束時間到達以后,對基於此id的活動記錄的狀態值進行修改的功能。代碼實現如下:

    @Override
    public int saveActivity(Activity entity) {
        int rows=activityDao.insertObject(entity);
        System.out.println("saveActivity.threadName="+Thread.currentThread().getName());
        //??????
        //開啟活動倒計時(活動到了結束時間應該將其狀態修改為0)
        //方案:(自己嘗試)
        //1)Java 官方:
        //1.1)Timer
        //1.2)ScheduledExecutorService
        //2)借助第三方的任務調度框架(任務調度框架,quartz)
        //方案1:Timer應用
        //1.1構建Timer對象
        Timer timer=new Timer();//此對象可以負責去執行一些任務(這個對象內置一個線程和一個任務隊列)
        //1.2啟動線程執行任務
        timer.schedule(new TimerTask() {//TimerTask為任務
            @Override
            public void run() {//一旦調用此任務的線程獲得了CPU就會執行這個任務的run方法
                System.out.println("updateState.threadName="+Thread.currentThread().getName());
                System.out.println("執行任務...");
                activityDao.updateState(entity.getId());
            }
        }, entity.getEndTime());//按指定時間執行任務.
        return rows;
    }

 現在這個功能的實現,就非常類似於我們現在互聯網中的惡意訂單,下了訂單就是不付錢,這種訂單我們一般情況下會把它理解成是惡意訂單,那么這種惡意訂單如何取消,就是應用的任務調度,時間到了自動去取消。這個基於Timer類實現的任務調度就完成了,但是這種Timer的方式,它有一個弊端,首先我們要執行的任務是屬於業務層的邏輯,另外,此Timer對象可以負責去執行一些任務,這個對象會內置一個線程和一個任務隊列,也就是說這個Timer類內的執行run的方法所在的線程,和Timer自身所在方法的線程是不一樣的。我們分別輸出一下這兩個線程:

    @Override
    public int saveActivity(Activity entity) {
        //...
        System.out.println("saveActivity.threadName="+Thread.currentThread().getName());
        //...
        Timer timer=new Timer();//此對象可以負責去執行一些任務(這個對象內置一個線程和一個任務隊列)
        //1.2啟動線程執行任務
        timer.schedule(new TimerTask() {//TimerTask為任務
            @Override
            public void run() {//一旦調用此任務的線程獲得了CPU就會執行這個任務的run方法
                System.out.println("updateState.threadName="+Thread.currentThread().getName());
                //...
            }
        }, entity.getEndTime());//按指定時間執行任務.
        return rows;
    }

重啟服務器,創建一個活動任務:

 時間到10:50時,看控制台輸出信息如下:

通過控制台的輸出,我們可以發現確實是兩個不同的線程,第一個saveActivity.threadName=http-nio-8080-exec-6,這個線程是屬於tomcat中的線程,而updateState.threadName=Timer-0,這個線程是Timer對象內置的線程,我們不可能讓tomcat的線程一直在這阻塞着,因為tomcat線程還要處理新的請求,如果很多類似的任務都阻塞了tomcat線程,那么tomcat就沒有辦法去處理新的請求了,那現在我們刷新下活動頁面,觀察此活動的狀態是否已經更改,我們可以發現狀態已經被修改為無效:

那這個活動結束時間后自動修改活動狀態的功能就實現完成的,但是還有一個問題,假設我們現在所有的人,比如說有一萬人去同時創建活動,那這個Timer對象是不是會構建一萬個,那這個Timer對象要構建一萬個的話,那一萬個Timer對象,就對應着一萬個線程對象,一個線程對象占用多少內存,一般操作系統給它分配的,默認是1M內存,一個線程占用1M,那一萬個線程就占用10個G的內存,可以想象,瞬間10個G的內存就沒有了,就算服務器內存比較多,並發量再大一點,可能系統就直接崩潰了,我們說線程數比較少無所謂,但是線程數一旦比較多,因為操作系統一般給我們線程默認分配的,就是1M內存,所以線程這塊,它是重量級的一個對象,占用資源比較多,創建線程比較耗時,那這時使用Timer類去實現任務調度就不合適了,因為Timer這里面存在一個弊端,即每次Timer它都會啟動一個新的線程,那有人想那就只由一個線程去執行,把Timer對象拿出去,放到一個工具類中去寫,就一個Timer去執行多個任務,那就會造成阻塞,因為一個線程多個任務,就會有大量的阻塞在里面,因為這些任務就會存到(Timer內置的)一個任務隊列了。

所以一般會用到線程池,也就是Java官方給出的第二種方式,ScheduledExecutorService,內置一個線程池。ScheduledExecutorService這個對象里面,它內置一個線程池,這個池中的線程可以重復應用,那我們可以通過這個池中的線程來處理多個並發請求,這樣的話,性能上至少會好一些,可能對於資源的利用來講,也會比較好,我們既要保證高效,又要實現低耗,還要保證線程安全,這是我們在程序開發中所要重點突破的點,雖然這個活動案例比較小,但很多地方都會有這樣的應用場景,麻雀雖小,五臟俱全。

還有第三種寫法,借助第三方的任務調度框架quartz,這個任務調度框架里面也使用了線程池技術,線程池是任務調度的基礎。

那對於Timer對象的簡單的用法就已經會用了,但是在我們這個程序中,如果在Timer的run方法中(內置的線程里),執行完了這個更新 activityDao.updateState(entity.getId());,還要再加一句timer.cancel();,就退出這個任務調度,因為這個任務都已經執行完了,還讓這個線程繼續運行着做什么呢,沒有必要了,執行結束以后,我們調用timer的cancel()方法,結束這個任務,此時線程就會退出,后續線程也會銷毀。

        //方案1:Timer應用
        //1.1構建Timer對象
        Timer timer=new Timer();//此對象可以負責去執行一些任務(這個對象內置一個線程和一個任務隊列)
        //1.2啟動線程執行任務
        timer.schedule(new TimerTask() {//TimerTask為任務
            @Override
            public void run() {//一旦調用此任務的線程獲得了CPU就會執行這個任務的run方法
                System.out.println("updateState.threadName="+Thread.currentThread().getName());
                System.out.println("執行任務...");
                activityDao.updateState(entity.getId());
                timer.cancel();//退出任務調度(后續線程也會銷毀)
            }
        }, entity.getEndTime());//按指定時間執行任務.

那還有人會想,我不想把這段代碼寫在這個saveActivity(Activity entity),這個方法里面,想把這段代碼,執行任務的這段內容提取出去,提到一個類里面去,然后我們在業務層的這個saveActivity方法中去調用提取到那個類里面的方法,如果還有其他模塊也用到了任務調度,我不想每個類里面都去寫任務調度的這段代碼,那其實我們想把這段代碼提出去,首先,我們在 activityDao.updateState(entity.getId());,這里我們可能需要傳一個id,這個是肯定要傳的,同時這個activityDao也要提出去,因為其他模塊如果也要用到這個ActivityDao,那就需要把這個dao也注入過去,如果我們不考慮dao的通用性,我們可以這樣做,專門去寫一個工具類,或者一個具體的對象,我們可以把這個具體的對象放在com.cy.pj.common.task包下, 就是執行任務的一個包,或者寫在com.cy.pj.activity.scheduled包下,然后在包下定義一個類TimerScheduledTask,基於Timer實現的任務調度類,那實際我們就可以把上述任務調度的代碼提取出來寫成一個方法,或者直接提取到我們寫的這個TimerScheduledTask工具類就可以,如果寫成一個方法,選中需要提取的代碼,然后Alt+Shift+M,提取成private void extracted(Activity entity),這樣的一個方法,但是我們這個ActivityDao只能在當前類中去使用;如果寫成一個工具類TimerScheduledTask,在其中定義一個靜態的方法public static void schedule(TimerTask task, Date time){...},這行任務,不要求有什么返回結果,在這個schedule方法中new一個Timer,開啟一個任務,接下來我們就可以在Service層調用這個工具類的schedule方法,傳入任務和時間,那如果推出這個任務,在TimerTask這個任務中也有cancel方法,我們可以讓這個任務cancel();

但這樣去寫的話,代碼量並沒有減少,意義不大,所以還是不建議提取這工具類,但這種抽取工具類的方式,需要去理解:

        //方案1:Timer應用
        //1.1構建Timer對象
        //Timer timer=new Timer();//此對象可以負責去執行一些任務(這個對象內置一個線程和一個任務隊列)
        //1.2啟動線程執行任務
        /*
         * timer.schedule(new TimerTask() {//TimerTask為任務
         * 
         * @Override public void run() {//一旦調用此任務的線程獲得了CPU就會執行這個任務的run方法
         * System.out.println("updateState.threadName="+Thread.currentThread().getName()
         * ); System.out.println("執行任務..."); activityDao.updateState(entity.getId());
         * timer.cancel();//退出任務調度(后續線程也會銷毀) } }, entity.getEndTime());//按指定時間執行任務.
         */        
        TimerScheduledTask.schedule(new TimerTask() {
            @Override
            public void run() {
                activityDao.updateState(entity.getId());
                cancel();
            }
        }, entity.getEndTime());

TimerScheduledTask工具類:

package com.cy.pj.activity.scheduled;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerScheduledTask {
    public static void schedule(TimerTask task, Date date) {
        Timer timer = new Timer();
        timer.schedule(task, date);
    }
}

 那這個Timer類中的schedule方法的第一個參數TimerTask能不能用lamda表達式的形式去寫呢,並不能,TimerTask是一個抽象類,不是一個接口,必須是函數式接口才可以寫成Lamda表達式形式,因為TimerTask里面,它還有其他的方法,要是只有一個方法就可以直接Lamda表達式。

 那以上,我們把保存活動記錄,並獲得返回自動主鍵的sql語句寫在了ActivityMapper.xml中,那還可以不寫在xml里,比方說以注解的方式去做,但對於復雜的sql,我們還是建議寫在xml中,這里我們注釋掉ActivityMapper.xml中的這段sql,再以注解的方式去實現這個sql語句:

 那現在我們來到ActivityDao.java,這個接口中,在dao中的 int insertObject(Activity activity) 方法上面,以注解的方式去實現保存活動記錄的功能,那我們直接在方法之上寫@insert,然后把之前在ActivityMapper.xml中寫好的sql語句直接復制過來:

     @Insert("insert into tb_activity\r\n" + 
             "(title,category,startTime,endTime,remark,state,createdUser,createdTime) \r\n" + 
             "values \r\n" + 
             "(#{title},#{category},#{startTime},#{endTime},#{remark},#{state},#{createdUser},now())")
     int insertObject(Activity activity);

 我們發現復制過來的sql,又是字符串拼接,又是換行符什么的,如果不想以這種換行的方式去寫的話,也可以把它寫成一行,那還有useGenenratedKeys和keyProperty,那這里也可以用@Options注解,同樣的去聲明它們,至於parameterType,參數類型就不用寫了,因為在insertObject(Activity activity)的方法上面,已經指定了這個Activity參數類型了,keyProperty所指定的id,就賦值給了這個Activity參數對象:

package com.cy.pj.activity.dao;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import com.cy.pj.activity.pojo.Activity;

@Mapper
public interface ActivityDao {
     //...    
     @Insert("insert into tb_activity (title,category,startTime,endTime,remark,state,createdUser,createdTime) values (#{title},#{category},#{startTime},#{endTime},#{remark},#{state},#{createdUser},now())")
     @Options(useGeneratedKeys = true, keyProperty = "id")
     int insertObject(Activity activity);
     //...
}

重啟服務,重新再創建一個活動,可以測試程序依然是可以正常運行的。

最后來到控制層ActivityController的doSaveActivity方法中,調用activityService.saveActivity(entity);方法,代碼如下:

package com.cy.pj.activity.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.cy.pj.activity.pojo.Activity;
import com.cy.pj.activity.service.ActivityService;

@Controller
public class ActivityController {
     @Autowired
     private ActivityService activityService;
     
     @RequestMapping("/activity/doSaveActivity")
     public String doSaveActivity(Activity activity) {
         System.out.println("activity="+activity);//檢查客戶端提交的數據
         activityService.saveActivity(activity);
         return "redirect:/activity/doFindActivitys";
     }
     
     /**查詢所有活動信息*/
     @RequestMapping("/activity/doFindActivitys")
     public String doFindActivitys(Model model) {
         List<Activity> list=activityService.findActivitys();
         model.addAttribute("list", list);
         return "activity";
     }
}

 然后訪問 http://localhost:8080/activity/doFindActivitys 點擊創建活動按鈕,在彈出窗口中填寫如下數據,頁面就多出一條我們添加的數據(時間的格式必須以yyyy/MM/dd的形式,不能寫成yyyy-MM-dd形式,因為SpringMVC有一個默認的時間格式,后面或說),默認狀態是無效,因為我們沒有給這個表設置默認值,那如果在這種沒有給默認值的情況下,希望在創建活動時,讓狀態為有效狀態,要如何實現呢,那我們就可以在Activity.java這個pojo對象里面,給這個狀態設置一個默認值,private Integer state=1;,默認值為1,我們創建活動默認就是有效狀態,如果將來需要無效狀態,就把數據里的那個狀態字段的值改為0即可:

 那到這,創建活動基本的業務流程就已經做完了,后續我們還可以在頁面表單提交之前,即點擊button時,可以對這個表單的內容做校驗,比如在activity.html頁面中,在doSaveObject()方法中,$("form").submit();,即這個表單提交之前,做一些表單校驗,具體的實現,我們可以通過js拿到表單中的內容,比如title不允許為空,時間格式不正確,這些都可以使用一些正則表達式,校驗成功則允許提交表單,否則,使其無法提交;另外這個活動無需驗證重復,因為這個活動每個用戶都可以創建,它的創建時間和創建用戶都不一樣,面向的用戶也不一樣,所以這里不需要驗證重復;還有如果時間格式不對的話,比如填寫的時間為2020-08-06的形式,那么就會報如下400異常:

 400異常,表示參數的個數,類型或格式不正確,這里就是由於時間參數的格式不正確所引起的:

 

因為SpringMVC默認可以處理的時間格式是yyyy/MM/dd,這樣的格式。那如果就想用yyyy-MM-dd的格式,那就得指定日期格式,那如何指定日期格式呢,我們需要在pojo這個類里面去指定,即在Activity.java中,比如startTime和endTime屬性,可以這樣寫:

@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date startTime;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date endTime;

注意,這是保存數據執行save操作時的格式設置,如果將來想把數據庫里面的數據取出來,以另外一種時間格式去呈現,那還有另外的格式設置方式,現在設置的僅僅是客戶端向服務端提交時候的那個數據格式,可以通過@DateTimeFormat注解在pojo中定義,即SprigMVC基於@DateTimeFormat指定的日期格式接收客戶端提交的數據,假如沒有指定格式,SpringMVC默認格式為yyyy/MM/dd,建議也用這種格式,格式中的位數必須與輸入的實際個數對應。

對於格式問題的解決方案,一般可以由前端去鎖定,不允許用戶自己輸入,只能通過日期框進行選擇:

那我們這里也可以去做一個類似這樣的功能,在我們的activity.html活動頁面,創建活動的模態框里,點擊開始時間時,也能夠彈出一個日期選擇框:

 那這個功能是如何實現的呢,用的就是基於Bootstrap前端框架擴展的第三方插件Bootstrap datepicker,見后面有實現;那到這里,保存操作的基本邏輯流程就 走通了,接下來再做一個刪除功能:

 點擊delete按鈕,我們可能是基於所選記錄的id進行刪除,如果基於id進行刪除,那我們在服務器端就需要在ActivityController中添加這么一個方法 doDeleteById,或者叫 doDeleteObject,然后這個方法需要一個入參id,這個id的類型最好用Long,因為我們的pojo類中根據數據庫字段定義的就是Long類型,與其保持一致。然后我們,在方法體內部把id打印一下,然后是返回值,刪除完成后,還可以再查詢一下,重定向到查詢的請求,我們可以先簡單一下,因為這里以重定向的方式並不理想,這里也可以直接在頁面上更新,后面再說,最后映射請求路徑為:"/activity/doDeleteObject"。代碼如下:

    @RequestMapping("/activity/doDeleteObject")
    public String doDeleteObject(Long id) {
        System.out.println("delete.id="+id);
        //...
        return "redirect:/activity/doFindActivitys";
    }

控制層先寫到這里,我們在客戶端做一些修改,看看能否在控制層ActivityController中拿到活動記錄的id,基本寫法如下:

 我們來到activity.html這個頁面,找到刪除按鈕的位置,<td><button type="button" class="btn btn-danger btn-sm">delete</button></td>,這里如果想用thymeleaf提供的方式觸發點擊事件函數,可以這樣寫,th:onclick="doDeleteObject([[${aty.id}]])",即

<td><button type="button" class="btn btn-danger btn-sm" th:onclick="doDeleteObject([[${aty.id}]])">delete</button></td>

其中,想把通過thymeleaf的取值方式,${aty.id},得到所需活動記錄的參數,做為js函數方法的入參傳給函數,則thymeleaf中規定,必須在其外層包裹兩個方括號(比如這里是[[${aty.id}]])才可以。這是thymeleaf中規定的格式:

<tbody>
    <tr th:each="aty:${list}">
        <td th:text="${aty.title}"></td>
        <td th:text="${aty.category}"></td>
        <td th:text="${#dates.format(aty.startTime, 'yyyy/MM/dd HH:mm')}"></td>
        <td th:text="${#dates.format(aty.endTime, 'yyyy/MM/dd HH:mm')}"></td>
        <td th:text="${aty.state==1?'有效':'無效'}"></td>
        <td><button type="button" class="btn btn-danger btn-sm" th:onclick="doDeleteObject([[${aty.id}]])">delete</button></td>
    </tr>
</tbody>

寫完這個點擊事件之后,我們后面需要定義doDeleteObject這樣的一個函數,並為這個函數聲明一個入參id,然后在函數體內,location.href="/activity/doDeleteObject?id="+id;,跳轉到某一個地址去執行刪除操作,當然,還可以給出一個提示,比如在點擊刪除按鈕后,提示用戶確定刪除么,if(!confirm("確定刪除么?")) return;,其中comfirm,它屬於JavaScript中的一個JS函數,會彈出一個提示窗口,如果點擊確定,confirm函數會返回一個true,非true就是false,return;就不會執行,因此會繼續執行當前函數中的后續語句,如果點擊取消,不刪除,confirm就會返回一個false,非fasle就是true,則執行語句return;,結束執行當前的doDeleteObject方法:

<script type="text/javascript">
   function doDeleteObject(id){
       if(!confirm("確定刪除么?"))return;
       location.href="/activity/doDeleteObject?id="+id;
   }
   //...
</script>    

執行效果如下:

 

點擊確定后注意,我們沒有真的刪除這條記錄,這時我們還沒有做刪除,看控制台執行結果,可以發現我們已經取到了這條記錄ID:

 拿到這個id以后,我們就可以繼續下一步操作,基於這個id進行刪除活動記錄的業務。另外,我們這里請求路徑傳參的方式是以問號對請求路徑與請求參數進行分割的,這是典型的java路徑傳參方式,那這里還可以使用更為普及的restful風格的方式進行傳參,即根據斜杠的方式傳參,實現方式如下:

     @RequestMapping("/activity/doDeleteObject/{id}")
     public String doDeleteObject(@PathVariable Long id) {
         System.out.println("delete.id="+id);
         //...
         return "redirect:/activity/doFindActivitys";
     }

 相應的客戶端,activity.html,請求路徑url上就不是寫問號了,直接是以斜杠連接:

function doDeleteObject(id){
 if(!confirm("確定刪除么?"))return;
 location.href="/activity/doDeleteObject/"+id;
}

我們可以發現,依然是可以拿到請求傳遞過來的參數id的,這樣我們就以傳統的java里的問號傳參方式和更為普及的restful的傳參方式,分別拿到了請求參數id,但其實有的時候,這里的活動創建了,是不允許刪除的,只不過我們為了便於對技術的理解而把刪除操作 也做了一下,很多活動是到時間是自動刪除的,當然這些需要根據具體的業務需求而定,比如一般要刪除的話,首先應該檢查活動的狀態是什么,如果活動狀態為有效,不允許進行刪除,即得到活動id以后,首先判斷這個活動是否正在進行中,如果是的話就不允許刪除此活動,所以我們在刪除活動之前,可能還需要在業務層做一個校驗,校驗活動當前的狀態值,如果活動狀態為0-禁止,則允許刪除,否則活動狀態為1-啟動,則活動正在進行中,不允許刪除,那這里,我們把刪除的業務操作寫一下,此時我們把id已經提交給后台,那基於id進行刪除,那首先是后台的數據層,在ActivityDao接口里面,我們可能會定義一個方法 int deleteById(Integer id);,或者寫這里寫成 int deleteObject(Integer id);,去基於id執行一個活動的刪除:

     @Delete("delete from tb_activity where id=#{id}")
     int deleteObject(Long id);

 那下一步,我們可能要去寫業務層,業務層接口ActivityService中也是定義一樣的方法: 

     int deleteObject(Long id);

 然后在業務層實現類ActivityServiceImpl.java中,實現這個deleteObject方法,那種真正的去執行delete刪除之前,我們還要做些什么呢,那我們剛才說過了,在刪除之前,我們還需要校驗活動的狀態,如果活動狀態為有效,正在進行中,那就不允許刪除,否則風險系數很高,你的活動沒有結束呢,如果來了一個人進入后台一看,一不小心把活動刪了,所以正在進行中的活動不允許進行刪除,那這個校驗呢,可以在客戶端校驗,如果在客戶端校驗的話,肯定是先在頁面上拿到了要刪除的那條活動記錄的狀態值,在點擊delete操作的時候,就拿到了相應活動記錄的狀態值,如果狀態值為有效,就不允許去提交這個刪除請求,這是在客戶端校驗,那也可以在服務端校驗,在服務端校驗,就是我們在服務端業務層執行刪除deleteObject操作時,在調用activityDao.deleteObject(id)方法之前,先查詢了活動記錄的狀態值,假如狀態有效就需要告訴客戶端,現在不能更新,但如何告訴客戶端這個活動狀態有效,不能更新,即服務端該怎么返回這個信息到客戶端,想要實現這個功能,最有效的方式是通過ajax去做的,不用ajax不是能做,太麻煩了,所以這里只需知道怎么去校驗即可,暫不去實現校驗功能:

    @Override
    public int deleteObject(Long id) {
        //校驗活動狀態?(正在進行中的活動不允許進行刪除)
        //.........
        //刪除活動
        int rows=activityDao.deleteObject(id);
        return rows;
    }

最后,我們在控制層ActivityController.java中,再調用一下業務層的這個實現類的deleteObject方法,控制層不做具體的業務,任何邏輯校驗,它只是調用業務對象來執行業務,它做的是流程的這個控制,以及請求和響應數據的一個處理,即控制層就是拿客戶端的數據,調用業務層方法,然后響應。

     @RequestMapping("/activity/doDeleteObject/{id}")
     public String doDeleteObject(@PathVariable Long id) {
         System.out.println("delete.id="+id);
         activityService.deleteObject(id);
         return "redirect:/activity/doFindActivitys";
     }

那重啟服務,刷新,點擊delete按鈕,確定刪除,發現數據就會真正的被刪除了:

 我們發現如果這樣一個一個刪除,太麻煩了,那將來能不能在這做一個全選, 一個一個刪除太麻煩了,當然是可以的;我們用模態框實現了創建活動的活動信息錄入新增功能,很多場景都會有這種模態框的實現,雖然不是我們自己寫一個模態框,但至少我們用了一下它,把它的內容改成自己的業務需求,以及刪除和查詢功能,我們都做了,將來隨着活動數據越來越多,我們可能還要做分頁,所以我們在此項目的基礎上,可能還會寫其他的一些業務,然后做一些業務方面的擴展,那我們如何基於我們的技術來實現我們的業務需求,那當我們還沒有這種以業務為中心地去理解技術實現的思路時,那思路和代碼哪個更為重要呢?比如,我們需要在創建活動時開啟倒計時,在刪除時需要對活動狀態進行校驗等等。那關於修改操作,最好是通過ajax去實現,因為修改這塊數據的回顯的寫法,需要引入大量的js腳本,可能會有一定的難度,而保存和刪除加一起也就5行代碼左右,易於去分析和理解。

 工作能力:公司的teamleader一般會對員工有這樣的一個定位,比如說,領導安排了一個任務,說這個業務你去把它實現一下,有人會說,我們沒有學過啊,這個會讓別人對你有看法,說明這個人不會去學習;也有人會說,您能告訴我是用什么技術么,我去學一下,說明這個人的主動學習能力會強,還有人可能會說,你給我一個晚上的時間,我明天給你一個結果,這就是非常有底氣的,說明這個人不僅僅自學能力很強,還可以通過查詢資料,找別人幫忙,能夠解決這個問題,萬一解決不了呢,那先答應下來,解決不了,明天再找理由唄!比說說我想了個鍾各樣的辦法,查了什么什么資料,結果沒有實現出來,我還需要一點點時間,一般teamleader還會給你一些時間。。。

那對於一些前端技術的實現,比如日期選擇框,工作后可能10幾年也不會寫這個東西,而是用現成的工具,那這個現有的工具怎么做,比如bootstrap datepicker前端插件,那這個插件可以去網上下載它的靜態資源,比如從github上下載的bootstrap-datetimepicker-master,這個就是datepicker官方里給出的內容:

 這個文檔里有Bootstrap v2,還有Bootstrap v3的,那我們這里用的是Bootstrap v3的版本,所以我們一定是看的sample in bootstrap v3, 這個里面的內容,從這個目錄里去找index.html:

 打開這個index.html,這個文件就是datepicker的demo案例,除了Bootstrap的css樣式文件和js腳本外,首先發現在這個demo的head里引入了一個datetimepicker的css:

 其次,它又在這個body體內的最下面的js腳本部分,引入了datepicker的js腳本,還引入了一個使用什么語言的js腳本,它這個語言用的是bootstrap-datetimepicker.fr.js,這個fr是法語的吧!

 再往后,它又說你在初始化時,可以以以下3中形式進行初始化:

 不管是什么,我們去應用一個技術,重點於會用,而不是把它的所有的技術細節都搞明白了,所以我們現在就去試驗,如何使用這個datepiker框架:

 那我們在這個activity的活動項目中就做了這樣一件事,在項目的src/main/resources/static目錄下添加了datepicker的這個靜態資源文件datepicker,其中包括css和js兩個子目錄

 

 我們發現css和js目錄下分別有兩個名稱相同的,但后綴一個是css,一個是min.css的兩個文件,有min標識的文件,是壓縮版的css,一般項目上線都是使用壓縮版的文件,在壓縮版的文件中去除了文件中的縮進結構,所有的代碼可能就在幾行之內,而未壓縮版的,即min標識的文件是我們開發時用的,它的可讀性就會好很多,保留了開發過程中原始的縮進與換行的標識。

首先我們引入boostrap-datepicker到我們的頁面里:

<head>
... ...
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/datepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet" media="screen">
</head>
<body>
... ...
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
<script src="/jquery/jquery.min.js"></script>
<!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
<script src="/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/datepicker/js/bootstrap-datetimepicker.min.js" charset="UTF-8"></script>
<script type="text/javascript" src="/datepicker/js/locales/bootstrap-datetimepicker.zh-CN.js" charset="UTF-8"></script>
... ... 
</body>

那對於datepicker如何使用,我們可以從網上或者去它的官方文檔里去搜索,下面在我們的activity項目中使用如下:

<body>
...
    <div class="form-group" >
        <label for="startTimeId" class="col-sm-2 control-label">開始時間</label>
        <div class="col-sm-10">
            <input type="text" class="form-control form_datetime"  name="startTime" id="startTimeId"
                placeholder="start time">
        </div>
    </div>
    <div class="form-group">
        <label for="endTimeId" class="col-sm-2 control-label">結束時間</label>
        <div class="col-sm-10">
            <input type="text" class="form-control form_datetime"  name="endTime" id="endTimeId"
                placeholder="end time">
        </div>
    </div>
...
</body>
<script type="text/javascript">
    //datetimepicker函數由bootstrap-datetimepicker.min.js定義,用於初始化日期控件
    $('.form_datetime').datetimepicker({//這里的form_datetime為input標簽中的class選擇器
        language: 'zh-CN',
        format: "yyyy/mm/dd hh:ii",
        autoclose: true
    })
</script>

其中,form_datetime一般是寫到input標簽的class屬性里面,然后通過類選擇器得到這個input控件;再調用datetimepicker函數,此函數是由datepicker.js系統底層指定的,而這個函數的作用就是,在我們的頁面加載完成以后,就初始化這個datetimepicker函數,language: 'zh-CN',為設置datepicker的語言環境為中文,format: "yyyy/mm/dd hh:ii",為設置日期的格式,hh:ii為小時和分鍾,autoclose: true,表示選擇日期以后自動關閉日期框窗口,這些規則都是由datepicker.js中定義,需要查詢datepicker規范去設置,我們在使用時應該根據自己的業務需求去查詢。效果圖如下:

我們發現,這里還需要選具體的小時分鍾,那如果不想選這個時間,這個datepicker怎么實現的,還是沒有這個設置,暫時不得而知,就用這個帶有小時分鍾的形式,那如果這里在activity.html這里帶了小時和分鍾,那么我們在pojo類Activity.java中就需要為其中的對應的日期屬性指定相應的格式,即在客戶端指定什么日期格式,那么在服務器端的這個日期格式就需要和客戶端的日期格式,必須是匹配的,否則可能會出現400異常:

 

對於日期框,也可以使用js的默認日期框(這樣就可以解決datepicker還必須選擇小時和分鍾的情況),直接把input的type屬性改為date即可,而無需下載其他樣式文件,但這種方式存在很多兼容性問題(經過試驗,不同的瀏覽器對date的支持並不友好,所以此處應用了datepicker日期插件):

另外,對於日期框,瀏覽器還可能出現自動記憶填充功能,解決方案如下:

 那接下來,我們點擊創建活動按鈕,創建一個活動保存到數據庫,title:企業活動,類型:交易培訓,開始時間:2020/08/07 10:20,結束時間:2020/08/07 11:20,備注:dafjdajf,看看是否能保存成功:

 我們至少先寫出來,如果說后面還有其他的一些需求,可以再去改,那么點擊save保存后,我們發現保存成功了:

 但是,我們發現在日期的位置,沒有顯示小時和分鍾,只有年月日,那我們還想顯示小時和分鍾,那說明我們日期的格式化在我們客戶端activity.html這里,還有點小問題,如果我們后面還想顯示小時和分鍾,就在客戶端獲取日期時,將其格式為'yyyy/MM/dd HH:mm'即可:

<tbody>
    <tr th:each="aty:${list}">
        <td th:text="${aty.title}"></td>
        <td th:text="${aty.category}"></td>
        <td th:text="${#dates.format(aty.startTime, 'yyyy/MM/dd HH:mm')}"></td>
        <td th:text="${#dates.format(aty.endTime, 'yyyy/MM/dd HH:mm')}"></td>
        <td th:text="${aty.state==1?'有效':'無效'}"></td>
        <td><button type="button" class="btn btn-danger btn-sm" th:onclick="doDeleteObject([[${aty.id}]])">delete</button></td>
    </tr>
</tbody>

實現效果如下:

到這里Activity活動模塊的基本任務就算完成了,那基於這個小案例,我們繼續擴展一些新的內容,什么是健康檢查,什么是熱部署,還有Lombok插件的應用等,首先我們先看一下健康檢查的應用,那什么為健康檢查,就是在SpringBoot當中,它還給我們提供了一個監控功能,比方說監控你的url和bean之間的這種映射,監控你的bean對象是否注入到我們的容器中,監控你的系統當中的一些環境的配置都可以,其實這個健康檢查無非就是一個監控功能。那要在STS中去應用它,首先我們需要在項目中的pom.xml配置文件中添加健康檢查的依賴:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

添加依賴以后,我們啟動服務,然后在瀏覽器中輸入如下地址:http://localhost/actuator/health (默認80端口可以省略端口號),如顯示UP,表示當前項目的狀態是ok的。

 假如希望查看更多的actuator的選項及信息,想監控更多的信息,可以在Springboot中的配置文件application.properties里添加如下語句(生成環境不加):management.endpoints.web.exposure.include=*,假如我們應用的是阿里雲構建的SpringBoot項目,那在創建項目時,它的application.properties文件中默認這句話就有,假如是spring.io.starter構建的SpringBoot項目,那就需要手動去添加上這句話:

更改了配置我們需要重啟服務,然后我們在瀏覽器地址欄再輸入一個地址:http://localhost/actuator/beans 查看所有的spring 容器中的 bean 信息:

 可以看到輸出了一堆的beans,但這個看起來很不舒服,我們可以通過Ctrl+F,去找自己寫的bean,比如activityService,默認情況下的輸出結構確實不夠爽,那想讓這個結構呈現的時候更加清楚,可以對谷歌瀏覽器安裝一個jsonView插件,就可以以更加結構化的方式去查看bean的信息了,但要安裝這個插件,首先能夠上Google網站才能安裝,或者是在本地有下載離線版的,當然如果在線安裝,則必須保證網絡是暢通的,比如說打開Google設置中的更多工具,有一個擴展程序,

 在打開的窗口中點擊左上角的菜單圖標,就可以找到打開 Chrome 網上應用店:

 

 打開Chrome網上應用店,搜索JsonView,可能會出現多個JsonView,哪一個評價好,就用哪一個,我們就選第一個2832人的就可以了,添加至Chrome:

 安裝成功以后,我們再次訪問http://localhost/actuator/beans ,就可以發現裝了JSONView插件以后,它會把文本以結構化的方式去顯示,然后搜索activityService,就可以找到我們自己的Service,其內容包括類型type: "com.cy.pj.activity.service.impl.ActivityServiceImpl",作用域scope: "singleton",沒有給它起別名aliases為空,具體這個類所在的位置resource: "file [...]",它依賴於是誰dependencies["activityDao"];這個Service是依賴於activityDAO的,這里都可以去查看的。

 利用瀏覽器去查看健康檢查的監控信息了解即可,我們重點要掌握的是健康檢查,還可以直接在sts工具的Boot Dashboard(Boot面板)中選中項目,查看其屬性(show properties):

首先我們必須要確保項目已經啟動了tomcat,然后打開Boot DashBoard面板,選中啟動項目,然后點擊右上側工具欄的一個小圖標Show Properties:

 打開ShowProperties視圖,首先看Request Mappings是映射,這映射里面就記錄了我們寫的添加:/activity/doSaveActivity,刪除:/activity/doDeleteObject/{id},和查詢:/activity/doFindActivitys,這里呈現了url對應的是哪一個方法,這就叫做監控;

 然后,還有一些Beans,在Beans這個環境里面,我們可以找到我們自己寫的bean,有activityController,activityDao和activityServiceImpl,其他的那些就不是我們自己寫的,系統底層自動生成的。

打開activityController,我們可以發現它所在的是哪個類,它所依賴的是activityServiceImpl這個對象,以及這個activityController需要注入給誰么,它不需要注入給誰;

然后這個activityServiceImpl呢,它依賴於activityDao,activityServiceImpl會注入給誰,注入給activityController,這個結構是非常清楚的:

 最后,看這個activityDao依賴於誰,activityDao依賴於一個是工廠sqlSessionFactory,一個是sqlSessionTemplate,依賴於這兩個對象,那activityDao,它會注入給誰呢,注入給我們的activityServiceImpl:

那url和Beans的信息都看完了,最后還有一個就是環境Env,比如這個applicationConfig: [classpath:/application.properties],這是我們自己的那個配置文件,我們自己配置的那個環境,會在這個位置顯示:

比如說,我們可以看到我們配置的,

健康檢查:management.endpoints.web.exposure.include=*

映射文件的路徑:mybatis.mapper-locations=classpath:/mapper/*/*.xml

連接池的配置:

spring.datasource.username=root

spring.datasource.url=jdbc:mysql:///dbactivity?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.password= ******

前端框架thymeleaf的配置:

spring.thymeleaf.prefix=classpath:/templates/modules/

spring.thymeleaf.cache=false
spring.thymeleaf.suffix=.html

日志的配置:logging.level.com.cy=debug

以上這是我們自己配置的內容,除了我們自己配置的內容,還可以看到這塊還有一些系統的環境systemEnvironment:

 比方說,默認情況下,我用的那個jdk在哪個位置:JAVA_HOME = C:\CGBSoft\First\jdk1.8.0_45,這里都屬於一些監控信息。那這個Show Properties窗口,在打開的時候可能沒有顯示,是有這種可能的,有的時候這個窗口它會卡頓,它反應慢,這時可以把這個窗口來回拖動一下,基本上都沒問題:

 那健康檢查其實就是SpringBoot提供給我們的一個監控,那通過這個監控,我們可以在監控里面得到什么樣的信息,解決什么樣的問題呢?首先在Request Mappings里,一看就是url映射,那么通過這里,可以去檢測什么問題呢,比方說可以通過此映射檢測404問題,404問題無非就是你訪問的url,在我們這個RequestMappings里面找不到對應的資源么,那就可以在這里看看你的url對應的資源是否存在;然后在Beans里面也有一部分信息,Beans這部分內容里面可以去解決這個問題,通過這里可以對Spring中的bean進行檢查,例如可以發現NoSuchBeanDefinitionException,當在這個Beans里面都找不到bean,那啟動時一定是沒有的,就是當我們出現了NoSuchBean這樣的異常,但又不知道是哪個bean,那首先要找到自己的那個類,在Beans這里就可以檢查一下;而Env這部分里面定義的是什么呢,就是我們說的那個配置信息,這部分配置信息包含我們自己定義的,也包含系統幫我們去定義的一些配置信息:

我們點開這個Env,可以看到如下信息:

 我們再點開applicationConfig,這個應用程序配置都是我們自己的配置:

 比方說:我們配置的端口server.ports:

 

 再比方這個commandLineArgs這個參數:

還有一些什么系統屬性systemProperties,有很多,比如說我們現在用的java指定的版本,java.specification.version=1.8;比如我cpu的一些配置信息,sun.cpu.isalist=amd64;還有我們的編碼方式sun.jnu.encoding = GBK;等等等等,這里都可以去看到:

 以及我們自己配置的那部分內容:

這個就叫做監控,也就是說所謂的,對url,對bean,以及系統環境的一個監控,在這里面我們都能看到。 

那下一個話題是熱部署,對於熱部署來講,其實在我們的項目當中,假如我們改了java代碼,我們還需要手動去啟動我們的tomcat服務器,才能夠去加載我們的這個改動,那么假如我們希望,當我們改了代碼以后自動去加載這些配置信息,那就需要在項目的pom.xml配置文件中添加一個依賴:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

這個依賴叫做自動部署依賴,如果我們沒有配置熱部署,沒有引入熱部署的包依賴,無論修改了項目中的任何代碼,控制台都沒有反應:

我們的tomcat服務器是不會自動重啟的,我們必須手動重新部署項目。而一旦我們配置了熱部署:

當修改一些文件時,tomcat服務器都會自動重啟,但是對於硬件配置比較低的電腦,不建議配置熱部署,而是手動去重啟,因為我們每寫一部分,它都要重啟一下tomcat,其實是非常占用內存的,因為重啟的過程,它需要涉及到資源的銷毀,資源的重建,如果內存配置比較低,那就不要去開啟熱部署了。那加載了熱部署,一旦我們修改了如下代碼,tomcat就會自動重啟:

(1) src/main/java目錄下的源代碼;

(2) src/main/resources目錄下的application.properties配置文件;

(3) src/main/resources目錄下的MyBatis的Mapper映射文件;

(4) 項目目錄下的包依賴的pom.xml配置文件;

比方說,我們在項目啟動類CgbActivity01Application.java的位置,加一個換行或者加一個輸出語句; 

 比如在application.properties配置文件中配置一個server.servlet.context-path,或者配置tomcat能支持的最多線程數:server.tomcat.threads.max=256 (就類似於任務調度時底層Tomcat線程還會去啟動另外一個線程Timer,啟動那個線程和Tomcat線程是不一樣的,Tomcat線程主要是負責處理客戶端的請求);

比方說,我們在映射文件ActivityMapper.xml注釋掉或解注釋掉一段sql語句;

比方說,在項目的pom.xml文件中添加或去掉一個依賴,比如這個健康檢查依賴:

以上資源的改動都需觸發熱部署,但是如果改動的是靜態資源,比如src/main/resources/templates下的html頁面,以及改動src/test/java目錄下的測試類的時候,都不會重啟,所以說,了解了項目結構以后,所謂的熱部署就是我們修改了某些資源以后,tomcat會重啟,來加載我們修改后的資源,而哪些資源修改后會觸發熱部署機制,自動重啟tomcat加載資源,哪些資源的改動並不會進行熱部署:

 在目錄結構中,有一個目錄是src,這個src的內容是不需要我們去寫的,我們在src/main/或者src/test等目錄下面寫的所有內容,都是存儲在src的目錄下;然后src目錄下面生成的.class文件,還有我們的配置文件,都會存儲到target目錄下面,但是我們是無法點開這兩個目錄去查看的,只有進入工作區才可以看到,再下面的HELP.md,mvnw和mvnw.cmd,這是使用maven工具創建項目時自帶的一些文件,即這個就是我們的model。

 說明:當我們修改了src/main/java目錄下的java文件或者修改了src/main/resources目錄下的配置文件時,默認都會重啟你的web服務器,但是修改了測試類或html文件不會自動重啟和部署,但是假如希望修改了html,在重啟tomcat的情況下也能看到頁面模板內容的變化,需要配置spring.thymeleaf.cache=false,這樣即便不重啟服務器,只需在瀏覽器端刷新下頁面也是可以進行實時的頁面更新加載的。

那熱部署到這里就告一段落了,下一個內容是Lombok插件的應用:那為什么要使用Lombok呢?首先在我們項目當中的會創建很多很多的pojo類,在這樣的類里面,我們寫好了屬性以后,我們會通過sources,自動生成一些set/get等方法,假如我們寫好了這個pojo類里面的屬性以后,想讓這些set/get/toString等方法由系統幫我們添加,而無需手動去添加,就可以通過Lombok幫我們實現,這樣的話,至少我們源代碼的代碼量會減少,即.java代碼不用我們自己去寫,但編譯后生成的.class文件里面會有,或者說在.java編譯成.class的時候,我們希望在這個class里面植入一部分代碼,那么就可以使用Lombok。

那么Lombok是什么,它就是一個第三方庫,即由別人寫好的,提供出來的一組API,我們可以基於Lombok中提供的API,在我們的.java程序編譯成.class文件的時候,自動給它織入一些方法,我們的點java程序中沒有這些方法,但是把.java程序編譯成.class文件的時候,Lombok會自動添加這些方法;

那么如何使用Lombok呢,首先添加依賴:

<dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
</dependency>

另外,SpringBoot其實默認集成了這個插件,那有的時候,如果不想這么去拷貝依賴,那也可以在我們的工程當中,選中pom.xml,右鍵選擇Spring,選擇Edit Starters,如果聯網還可以的話,就可以在彈出的Edit Spring Boot Starters這里搜索,找到Lombok插件:

 

如果之前用過Lombok,就會顯示Lombok默認已經選中了:

 

 那我們已經在項目當中,把Lombok的依賴添加上了,可以在Maven Dependencies目錄下找到這個依賴:

 可以在MavenDependencies下找到Lombok,復制它的Copy Qualified Name,然后在windows窗口直接輸入此地址,直接回車:

發現回車不可以,說明了我這個jdk,它不是安裝版的,它是配置了一個綠色版的,在這里面拷貝過來了,如果要是一個直接安裝版的jdk,就可以直接把Copy Qualified Name,這個復制的.jar文件的地址粘貼到windows窗口上,一回車就會去自動運行它。

 

 那如果不是安裝版的jdk,可以去掉Copy Qualified Name后的文件名,進入到lombok.jar文件所在的目錄中:

我們可以選中這個lombok的jar文件,鼠標右鍵打開命令行,如果這樣找不到的命令行,還可以在這個目錄的windows窗口上直接輸入cmd,或者通過win+R+cmd進入命令行,然后輸入目錄地址切換到此目錄下,通過java -jar lombok-1.18.12.jar命令,回車即可(說明:有的可能雙擊.jar文件就可以執行,有的可能雙擊不了,因為jdk是解壓版的,不是直接安裝的。 ):

然后,可能會彈出一個警告框Can't find IDE,不用去管它,直接點擊確定即可:

然后,在Specify location..這個位置選擇你的sdk要安裝在什么位置:

這里要確保現在的Lombok裝在正在使用的那一個STS的工具上面(已經添加有lombok依賴),然后點擊Select:

 然后點擊install:

最后顯示安裝成功,叉掉這個窗口即可:

 這里需要注意的是,STS所在的路徑中不允許中文或特殊符號,比如$&等,否則我們的STS可能就啟不來了,還得需要做額外的一些配置,假如按照成功以后,在我們的STS所在的目錄下就會多出一個lombok.jar文件,有了這個文件以后,那接下來還得去修改一下STS所在目錄下的SpringToolSuite4.ini文件:

 在這個SpringToolSuite4.ini文件里面,它會對lombok做一個自動的配置,打開這個文件,它會多出這么一行:javaagent:D:\CGBIIICLASS\TOOLS\sts-4.7.0.RELEASE\lombok.jar:

 即安裝好lombok以后,就會在此文件里多出這么一行記錄,那么多出這么一行記錄,它指定了你安裝的這個lombok.jar,這個文件所在的目錄,即我們這個STS工具的目錄下多出的lombok.jar所在的目錄,那么它就會在這個ini配置文件有這樣的一個記錄,假如這個指定的目錄存在中文或特殊符號,比如&符號,它最終在編譯的時候,有可能會在&符號這里替換一個斜杠,那么lombok就找不到了,找不到以后,那這個位置可能就要報錯了,STS工具啟動都起不來了:

所有步驟安裝完成以后,我們重啟STS工具,看看STS是否能成功啟動,如果可以正常啟動,說明安裝到這里是沒有問題的,那啟動成功以后,下一步就可以去驗證我們的lombok是否安裝成功或者說lombok有效了,我們可以在CGB-ACTIVITY-01這個項目里面,把這個pojo類Activity.java中的所有的set/get方法,以及toString方法全部去掉,只留有屬性信息,這些屬性會提示有警告標識,然后我們在Activity類之上去追加一個注解@Data,當我們加上這個@Data注解以后,就會發現這些屬性上的警告沒有了,這時就說明我們的lombok已經安裝成功,而且可以正常使用,即系統當中會幫我們把這個類的屬性,自動生成相應的get/set等方法,那么在我們這個Activity.java這個類上,就正確的使用了lombok里面的特性,在這個pojo類編譯成.class文件的時候,lombok自動會幫我們添加set/get以及toString方法。

 Lombok安裝成功的標志,第一個,在我們的開發工具sts當中會有一個lombok.jar文件,第二個,在我們的開發工具sts根目錄下的SpringToolSuite4.ini這個配置文件里面,會在最后多出一行信息,指明了lombok.jar文件所在的路徑,這里即sts的根路徑,需要注意的是這個lombok.jar文件的所在的目錄如果有中文字符,那么開發工具sts可能就啟動不了了,此時可以將這個lombok.jar文件剪切到一個英文的目錄下面,然后去修改一下這個SpringToolSuite4.ini文件中相應配置信息的路徑位置。
安裝好以后,首先我們可以重啟安裝了lombok的IDE,然后應用lombok注解(例如@Data注解),BUG分析:1.那當我們使用lombok注解應用無效時,首先確定正在使用的STS安裝的lombok,如果還不行的話,強制maven update更新項目。還有一些其他常見的問題(FAQ):第一個,STS安裝了lombok以后,創建新的項目是否還需要再次安裝lombok?答:不需要,但需要添加lombok依賴,但如果要是把項目拷貝到另一個STS開發工具時,那么這個STS必須也要安裝lombok,否則就無法應用lombok注解;第二個,lombok安裝了以后,如果對lombok進行卸載,這個卸載就兩個步驟,第一步刪除lombok.jar文件刪除(如果是開發工具使用的是idea也是同樣如此),然后第二步把sts根目錄下的SpringToolSuite4.ini文件里指定lombok.jar文件的那一行信息也刪掉即可;

那lombok安裝成功以后, 我們要想應用lombok,首先把安裝了lombok的開發工具重新啟動一下,然后在指定的類上面,比如Activity.java這個類來講,在其上添加@Data注解,當我們這個類在編譯時(將.java文件編譯成.class文件),lombok會自動基於類中的屬性添加相關方法到class文件,我們應該知道,對於我們sts這個軟件來講,在我們Ctrl+s一保存,我們寫的.java就會編譯成.class文件,那我們現在安裝了lombok以后,在我們的sts開發工具的SpringToolSuite4.ini配置文件中會多出javaagent這么一行信息,這個javaagent的意思就是當你現在這個STS當中,Ctrl+s去保存這個被lombok注解修飾的類的時候,比如這個Activity.java,系統底層會檢測這個java程序上面,也就是.java類上面有沒有lombok的注解,如果有的話,那么lombok,也就是lombok.jar這個文件就會啟動它內部的API,為我們這個.class文件添加set和get方法,還有toString方法都會有,那實際就是說目前,我們使用lombok,它把原有STS對我們.java程序進行編譯的那個過程做了一定的攔截,

攔截到以后,有lombok.jar來進行我們的.java程序的編譯,把編譯好的一些內容,比如Activity.java這個類,編譯好了以后,還會向這個類里面再添加一些set和get方法,那我們已經在Activity .java這個類里面添加了@Data注解,那lombok是否真的已經為我們添加了set和get方法,我們可以測試一下,比如我們在src/test/java/com/cy這個測試包下面,專門在這寫一個測試類LombokTests,看一下有沒有這個lombok,首先new一個Activity aty,然后aty.setId(100L);,aty.setTitle("互動答疑0");,發現是可以調用set方法的,那么這個set方法,我們並沒有去寫,但是生成了,我們在輸出一下aty,System.out.println(aty);,執行此測試方法,單元測試成功:

這里我們輸出了對象的內容,而不是對象的那種地址的表現形式,那么說明我們這里面除了添加了set和get方法,那么在輸出對象的時候,也調用了對象的toString方法,而我們也沒有重新toString,說明在我們Activity這個類的上面加了@Data以后,它幫我這個類生成了set/get,還有toString方法,還有hashcode,還有equals方法,它其實都幫我們生成了,都是由@Data注解描述此類時,但是如果只想要set和get方法,不想要其他的方法,那就可以直接加@Setter和@Getter,去掉@Data,這時就沒有所謂的toString方法了,這時再對這個對象輸出時,就只是我們的類全名和對象的hashcode值,比如com.cy.pj.activity.pojo.Activity@28a9494b,那如果我們再在這個類上加上@AllArgsConstructor,此時會發現這個Activity這個類就會報錯,因為@AllArgsConstructor表示會在這個類里面添加一個全參的構造函數,我們在這個類當中添加了帶參的構造函數,那么無參構造函數的默認就沒有了,所以這是在LombokTests這個測試類中的測試方法testLombok,在new Activity()無參構造器的時候就會報錯:

 那我們在Activity.java這個pojo類中再添加一個無參的構造器,添加注解@NoArgsConstructor,如果還想添加toString方法,就添加注解@ToString:

 這里的這些注解僅僅起到一個標識性作用,真正在這個類里面添加方法,不是說注解去添加,注解起到了這個標識以后,是底層會賦予這個類一些額外的特性,所以這里面要對這個注解要有一定的認識,這些這些可以被稱為元數據,即描述數據的數據都叫做元數據,這里提到的都是最常用的,當然還有其他的注解,比如說,我們在我們的測試類LombokTests上,又加了一個注解@Slf4j,然后在testLombok方法內的最后,再做一個輸出,log.info("title is {}",aty.getTitle());,可以發現我們在這個類里可以直接使用log,但是我們在這個類里面並沒有定義log,卻依然可以使用它,只是因為我們加了一個@Slf4j注解,我們這個log就會自動生成;這個Slf4j之前有寫過,是這樣的,private static final Logger log = LoggerFactory.getLogger(LombokTests.class);,都是利用這個Slf4j這個包下面的,這里還使用了一個設計模式,叫門面模式(阿里手冊有說明)。假如在LombokTests類中加上這樣一句話,那么lombok的@Slf4j這個注解就不起作用了,有一個警告:

 為什么@Slf4j上有一個警告,是因為當我們自己去創建private static final Logger log = LoggerFactory.getLogger(LombokTests.class);,這樣一個log對象以后,lombok就不再為我們創建了,那這個@Slf4j就寫也行,不寫也是可以的,其實就不用寫了;那我們加了@Slf4j這個注解以后,那系統底層會基於這個注解的描述,為這個LombokTests這個類里面自動生成這樣的一行,private static final Logger log = LoggerFactory.getLogger(LombokTests.class);,那這句話就可以注釋掉了,這個log對象照樣可以使用,即當我們的類上使用了@Slf4j注解時系統底層會自動為我們的類添加一行聲明log對象的代碼,但是這行代碼中參數中傳入的類加載器的類名,是@Slf4j這個注解描述哪個類,這個參數就是哪一個類的點class文件,我們運行看一下它的輸出:

 單元測試成功,上面是toString方法,下面就是INFO就是我們自己輸出的com.cy.LombokTests :title is  互動d答疑,Lombok具體的日志信息,這就是Lombok的應用,但是當我們引入了@Slf4j注解以后,發現這個log沒有生成,說明這個STS這個軟件有可能Lombok沒有安裝成功。

 

那么Lombok解決的問題就是,當我們寫了一個.java程序,我在這個.java程序里面可能沒有寫LoggerFactory.getLogger(LombokTests.class);這樣的語句,lombok可以幫我們自動生成這樣的一條語句,也就是說我們的程序會變的,好像越來越智能了,有些代碼不用我們寫,也能夠生成,這都是底層幫我們做的,比如說Lombok幫我們做的。Lombok僅僅起到的作用是讓我們自己寫程序的時候簡單一點,其實它的.class文件一點都不簡單,它的.class文件生成的還是和我們原先寫的沒有Lombok簡化之前的代碼的時候一樣的內容,只是lombok幫我們在寫java代碼的時候

 

附錄A:基本模板

使用以下給出的這份超級簡單的 HTML 模版,或者修改這些實例。我們強烈建議你對這些實例按照自己的需求進行修改,而不要簡單的復制、粘貼。

拷貝並粘貼下面給出的 HTML 代碼,這就是一個最簡單的 Bootstrap 頁面了。

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支持 HTML5 元素和媒體查詢(media queries)功能 -->
    <!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 -->
    <!--[if lt IE 9]> <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script> <![endif]-->
  </head>
  <body>
    <h1>你好,世界!</h1>

    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

JavaScript 在stackover.com 程序員的交流營中,最受歡迎的編程語言之一:JavaScript是客戶端最強悍的一種腳本語言,因為它的生態做的特別好,不僅僅在瀏覽器端,手機端,都可以去使用。

附錄B:基本模板

一、server.servlet.context-path配置的作用

定義: server.servlet.context-path= # Context path of the application. 應用的上下文路徑,也可以稱為項目路徑,是構成url地址的一部分。

server.servlet.context-path不配置時,默認為 / ,如:localhost:8080/xxxxxx

當server.servlet.context-path有配置時,比如 /demo,此時的訪問方式為localhost:8080/demo/xxxxxx

二、springboot 2.0變革后的配置區別

1、springboot 2.0之前,配置為 server.context-path

2、springboot 2.0之后,配置為 server.servlet.context-path

三、一個思考

原來的運營項目(已上線),配置文件添加 server.servlet.context-path 配置后,需要在thymleaf 中進行action請求的追加嗎?

答案:不需要。

栗子:

前端頁面采取form請求

<form th:action="@{/user/userLogin}" method="post" id="userLogin"></form>

action攔截接受方式

 
@Controller
 
@RequestMapping("/user")
 
public class LoginController {
 
 
 
@PostMapping("/userLogin")
 
public String userLogin(HttpServletRequest request, Model model) {
 
 

原項目的基礎上,追加一個配置

 
server:
 
port: 8080
 
servlet:
 
context-path: /demo

只需要再開始進入首頁時,追加 localhost:8080/demo ,后續的thymleaf中的href和action等無需添加/demo 。

參考文獻:https://blog.csdn.net/qq_38322527/article/details/101691785

鍵key上為什么有兩個冒號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM