SpringBoot 2.X 快速掌握
0、重寫博文的原因
- 當初我的SpringBoot系列的知識是採用分節來寫的,即:每一個知識點為一篇博文,但是:最近我黴到家了,我發現有些博文神奇般地打不開了,害我去找當初的markdown筆記,但是方便的話還是線上版舒服,只要有網就可以訪問,因此昨天晚上東拼西湊搞出了這篇SpringBoot基礎系列知識
1、什麼是springboot?
1.1、老規矩:百度百科一下
2、對springboot快速上手
2.1、通過官網來建立 - 瞭解
這裡面的建立方式不做過多說明,只需要在官網裡面建立好了,然後下載解壓,就可以了,我這裡直接使用編輯器建立
springboot搭建專案官網
2.2、使用IDEA編輯器建立
選完之後,idea就會去拉取相應的jar包,建立結果如下:
啟動專案
編寫controller
重新啟動主方法,輸入請求
這樣就建立成功了一個springboot專案
3、小彩蛋 - banner
上面這玩意兒,我不想看到它
-
在專案的resources資源目錄下,新建一個banner檔案
-
再執行專案主方法
- 至於這個banner為什麼可以啟動,在下一篇小部落格中說SpringBoot的原理圖時,裡面有
4、瞭解yml語法
- 這玩意兒的語法就像如下圖的類與屬性的關係一樣,層層遞進的( 注意:使用yml語法時,每句的結尾別有空格,容易出問題,另外:IDEA中採用tab縮排沒問題,但是:在其他地方,如:linux中,使用yml語法時,別用tab縮排,也容易導致程式啟動不起來 )
4.1、使用yml給實體類賦值
- 準備工作:匯入依賴
<!-- 這個jar包就是為了實體類中使用@ConfigurationProperties(prefix = "person") 這個註解而不報紅 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
-
使用
@ConfigurationProperties
註解實現給實體類屬性賦值
- 測試
5、jsr303檢驗
- jsr303這是資料檢驗的規範,基於這個的實現方式有好幾個,自行百度一下,然後註解含義都是和下面列出來的差不多
依賴
<!--JSR303校驗的依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
使用jsr303檢驗
- 可以搭配的註解如下:
空檢查
@Null 驗證物件是否為null
@NotNull 驗證物件是否不為null, 無法查檢長度為0的字串
@NotBlank 檢查約束字串是不是Null還有被Trim的長度是否大於0,只對字串,且會去掉前後空格.
@NotEmpty 檢查約束元素是否為NULL或者是EMPTY.
Booelan檢查
@AssertTrue 驗證 Boolean 物件是否為 true
@AssertFalse 驗證 Boolean 物件是否為 false
長度檢查
@Size(min=, max=) 驗證物件(Array,Collection,Map,String)長度是否在給定的範圍之內
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期檢查
@Past 驗證 Date 和 Calendar 物件是否在當前時間之前,驗證成立的話被註釋的元素一定是一個過去的日期
@Future 驗證 Date 和 Calendar 物件是否在當前時間之後 ,驗證成立的話被註釋的元素一定是一個將來的日期
@Pattern 驗證 String 物件是否符合正則表示式的規則,被註釋的元素符合制定的正則表示式,regexp:正則表示式 flags: 指定 Pattern.Flag 的陣列,表示正則表示式的相關選項。
數值檢查
建議使用在Stirng,Integer型別,不建議使用在int型別上,因為表單值為“”時無法轉換為int,但可以轉換為Stirng為”“,Integer為null
@Min 驗證 Number 和 String 物件是否大等於指定的值
@Max 驗證 Number 和 String 物件是否小等於指定的值
@DecimalMax 被標註的值必須不大於約束中指定的最大值. 這個約束的引數是一個通過BigDecimal定義的最大值的字串表示.小數存在精度
@DecimalMin 被標註的值必須不小於約束中指定的最小值. 這個約束的引數是一個通過BigDecimal定義的最小值的字串表示.小數存在精度
@Digits 驗證 Number 和 String 的構成是否合法
@Digits(integer=,fraction=) 驗證字串是否是符合指定格式的數字,interger指定整數精度,fraction指定小數精度。
@Range(min=, max=) 被指定的元素必須在合適的範圍內
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 遞迴的對關聯物件進行校驗, 如果關聯物件是個集合或者陣列,那麼對其中的元素進行遞迴校驗,如果是一個map,則對其中的值部分進行校驗.(是否進行遞迴驗證)
@CreditCardNumber信用卡驗證
@Email 驗證是否是郵件地址,如果為null,不進行驗證,算通過驗證。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
6、yml多環境配置
還有一種標準的配置,即:採用多個yml檔案,如:application-test.yml 就是測試環境的配置
、appilication-dev.yml 就是開發環境的配置
、appilication-pro.yml 就是生產環境配置
7、設定預設首頁
- 這是SpringBoot + thmeleaf響應式程式設計的技術,現在前後端分離,這種東西其實沒什麼鳥用
7.1、頁面在static目錄中時
-
直接在controller中編寫跳轉地址即可
7.2、頁面在templates模板引擎中時
這種需要匯入相應的啟動器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
編寫controller
測試
8、簡單認識thymeleaf
- 這是SpringBoot + thmeleaf響應式程式設計的技術,現在前後端分離,這種東西其實沒什麼鳥用
官網學習地址
8.1、什麼是thymeleaf?
一張圖看明白:
解讀:
-
前端交給我們的頁面,是html頁面。如果是我們以前開發,我們需要把他們轉成jsp頁面,jsp好處就是當我們查出一些資料轉發到JSP頁面以後,我們可以用jsp輕鬆實現資料的顯示,及互動等
-
jsp支援非常強大的功能,包括能寫Java程式碼,但是,SpringBoot是以jar的方式,不是war,第二,我們用的還是嵌入式的Tomcat,所以,springboot現在預設是不支援jsp的
-
那不支援jsp,如果我們直接用純靜態頁面的方式,那給我們開發會帶來非常大的麻煩,那怎麼辦?
SpringBoot推薦使用模板引擎:
-
模板引擎,jsp就是一個模板引擎,還有用的比較多的FreeMaker、Velocity,再多的模板引擎,他們的思想都是一樣的,SpringBoot推薦使用thymeleaf
-
模板引擎的作用就是我們來寫一個頁面模板,比如有些值,是動態的,我們寫一些表示式。而這些值從哪來?就是我們在後臺封裝一些資料。然後把這個模板和這個資料交給模板引擎,模板引擎按照我們封裝的資料把這表示式解析出來、填充到我們指定的位置,然後把這個資料最終生成一個我們想要的內容從而最後顯示出來,這就是模板引擎。
-
不管是jsp還是其他模板引擎,都是這個思想。只不過,不同模板引擎之間,他們可能語法有點不一樣。其他的就不介紹了,這裡主要介紹一下SpringBoot給我們推薦的Thymeleaf模板引擎,這模板引擎,是一個高階語言的模板引擎,他的這個語法更簡單。而且功能更強大
-
8.2、thymeleaf的取資料方式
- 官網中有說明
提取出來看一下,從而在springboot中演示一下
- 簡單的表達:
- 變量表達式: ${...}
- 選擇變量表達式: *{...}
- 訊息表達: #{...}
- 連結 URL 表示式: @{...}
- 片段表示式: ~{...}
8.3、在springboot中使用thymeleaf
依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
怎麼使用thymeleaf?
-
這個問題換言之就是:html檔案應該放到什麼目錄下
- 前面我們已經匯入了依賴,那麼按照springboot的原理( 本篇部落格結尾附有原理連結 ),底層會幫我們匯入相應的東西,並做了相應的配置,那麼就去看一下原始碼,從而知道我們應該把檔案放在什麼地方( 注:springboot中和配置相關的都在xxxxxProperties檔案中,因此:去看一下thymeleaf對應的thymeleafProperties檔案 )
- 那就來建一個
-
編寫controller,讓其跳到templates目錄的頁面中去
-
測試
-
成功跳過去了
8.4、延伸:傳輸資料
8.4.1、開胃菜
- 參照官網來( 這裡只演示 變量表達式: ${...},其他的都是一樣的原理 )
編寫後臺,存入資料
在前臺獲取資料
表空間約束連結如下,這個在thymeleaf官網中有
xmlns:th="http://www.thymeleaf.org"
測試:
8.4.2、開整
後臺
前臺
測試
其他的玩法都差不多
9、靜態資源處理方式
- 在前面玩了thymeleaf,在resources中還有一個目錄是static
-
那麼就來研究一下靜態資源:靜態資源,springboot底層是怎麼去裝配的?
- 都在WebMvcAutoConfiguration有答案,去看一下
- 通過上述的原始碼發現兩個東西:
webjars
和getStaticLocations()
9.1、webjars的方式處理靜態資源
- webjars的官網:https://www.webjars.org/all
- 進去之後:裡面就是各種各樣的jar包
使用jQuery做演示
- 匯入jQuery的依賴
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
- 匯入之後:發現多了這麼一個jar包,現在我們去直接訪問一下
- 是可以直接訪問的,為什麼?
getStaticLocations(),點進去看一下
發現是如下這麼一個方法
public String[] getStaticLocations() {
return this.staticLocations;
}
- 檢視
staticLocations
"classpath:/META-INF/resources/", <!--這個就不多說明,指的就是再建一個META-INF資料夾,裡面再建一個resources目錄,參照Java基礎中的web專案目錄-->
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
- 發現有四種方式可以放靜態資源,那就來測試一下
9.1.1、resources/ static/ public的優先順序
測試
- 發現resources下的優先順序最高
刪掉resources中的資原始檔,繼續測試
- 發現static目錄其次
9.1.1.1、總結:resources、static、public優先順序
- resources目錄下的優先順序最高
- 其次是static
- 最後是public
資源放置建議:
- public放置公有的資源,如:img、js、css....
- static放置靜態訪問的頁面,如:登入、註冊....
- resources,應該說是templates,放置動態資源,如:使用者管理.....
10、整合jdbc、druid、druid實現日誌監控
10.1、整合jdbc、druid
依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
編寫application.yml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
username: root
password: "072413"
測試
10.2、整合druid
依賴
<!--要玩druid的話,需要匯入下面這個依賴 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
修改yml檔案
測試
10.3、druid實現日誌監控
- 注意點:需要web啟動器支援
<!--
玩druid實現監控日誌,需要web啟動器支援,因為:druid的statViewServlet本質是繼承了servlet
因此:需要web的依賴支援 / servlet支援
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
編寫配置
package cn.xiegongzi.config;
// 這個類是為了延伸druid的強大功能 ————— 監控後臺
// 注意:這個需要spring的web啟動器支援,即:這個監控後臺的本質StatViewServlet就是servlet,所以需要servlet支援
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean StatViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
HashMap<String, String> initParameters = new HashMap<>();
// 下面這些引數可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父類 com.alibaba.druid.support.http.ResourceServlet 中找到
initParameters.put("loginUsername", "zixieqing"); // 登入日誌監控的使用者名稱
initParameters.put("loginPassword", "072413"); // 登入密碼
initParameters.put("allow", "`localhost`"); // 執行誰可以訪問日誌監控
bean.setInitParameters(initParameters);
return bean;
}
}
測試
11、整合mybatis
- 注:複雜sql使用xml,簡單sql使用註解
11.1、xml版
匯入依賴
<!--
mybatis-spring-boot-starter是第三方( mybatis )jar包,不是spring官網的
spring自己的生態是:spring-boot-stater-xxxx
-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
編寫實體
package cn.xiegongzi.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private Integer id;
private String username;
private String password;
}
編寫dao / mapper層
package cn.xiegongzi.mapper;
import cn.xiegongzi.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/*
* @Mapper 這個註解是mybati-spring提供的,即:和自動裝配是一樣的效果
* 還可以用:
* @Repository 是spring本身提供的
*
* 以及:在啟動類( main )中使用@mapperScan掃包
* */
@Mapper
public interface IUserMapper {
List<User> findALLUser();
}
編寫xml的sql語句
- 注意點:dao層 / mapper和xml的同包同名問題
編寫yml
# 編寫連線池
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
username: root
password: "072413"
type: com.alibaba.druid.pool.DruidDataSource
# 把實現類xml檔案新增進來
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: cn.xiegongzi.entity # 給實體類配置別名
configuration:
map-underscore-to-camel-case: true # 開啟駝峰命名對映
測試
11.2、註解版
- 和ssm整合中的玩法一樣,只改動一個地方即可,就是不需要xml了
- 直接在dao層 / mapper的介面方法頭上用
@insert()
、@delete()
、@update()
、@select()
註解,然後小括號中編寫sql字串即可
- 當然:也可以給日誌設定級別
12、整合pageHelper分頁外掛
依賴
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
測試
13、整合swagger
- 理論知識濾過,自行百度百科swagger是什麼
- swagger的常見註釋使用和解讀網址:http://c.biancheng.net/view/5533.html
13.1、快速上手
匯入依賴
<!--swagger所需要的依賴-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<!--這個依賴是為了渲染swagger文件頁面的( 為了好看一點罷了 ) ,swagger真正的依賴是上面兩個-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
編寫swagger配置檔案
package cn.xiegongzi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration // 表明當前類是一個配置類,並把當前類丟到spring容器中去
@EnableSwagger2 // 開啟swagger功能
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
// http://ip地址:埠/專案名/swagger-ui.html#/
ApiInfo apiInfo = new ApiInfoBuilder()
// 網站標題 即:生成的文件網址標題
.title( "悠忽有限公司" )
// 網站描述 即:對生成文件的描述
.description( "這是一個很nice的介面文件" )
// 版本
.version( "9.0" )
// 聯絡人
.contact( new Contact("紫邪情","https://www.cnblogs.com/xiegongzi/","110" ) )
// 協議 http / https都可以
.license( "tcp" )
// 協議url 即:進入到swagger文件頁面的地址
.licenseUrl( "http://localhost:8080/" )
.build();
// swagger版本
return new Docket( DocumentationType.SWAGGER_2 )
// 請求對映路徑 就是:controller中有一個介面,然後前臺訪問的那個介面路徑
// 這個可以在生成的文件中進行除錯時看到
.pathMapping( "/" )
// 根據pathMapping去進行查詢( 做相應的操作 )
.select()
// 掃描包 即:哪些地方可以根據我們的註解配置幫我們生成文件
.apis( RequestHandlerSelectors.basePackage( "cn.xiegongzi" ) )
.paths( PathSelectors.any() )
.build()
.apiInfo( apiInfo );
}
}
編寫yml檔案
spring:
datasource:
# 注意這裡加了cj啊,即:MySQL驅動用的是8.x的
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
username: root
# 注意:在yml中,這種自己寫的內容最好用字串寫法,以後玩Redis也是一樣,不然有時出現坑,即:密碼無效 / 這裡面填入的值沒有解析出來,不匹配
password: "072413"
編寫實體類
package cn.xiegongzi.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
/**
* @ApiModel(description = "描述")
* 表明這個實體類就是需要的資料名和型別
* 後臺接收前端的引數是一個物件時使用( controller寫的是@RequestBody OrderPaidDTO orderPaid ),即:後端接收引數封裝成了一個xxxxDTO( PO、BO、Entity、DTO、DAO含義和關係是什麼,自行百度 )
* 這個東西可以先不加,在做增加、修改時可以用這個測試一下,從而去swagger中看效果
*/
@ApiModel(description = "使用者資訊")
public class User implements Serializable {
// 資料屬性配置,這裡面可以跟幾個屬性,常見的是value、required、dataType、hidden,在待會後面附加的連結中有解釋
@ApiModelProperty
private Integer id;
@ApiModelProperty
private String username;
@ApiModelProperty
private String phone;
}
編寫mapper
package cn.xiegongzi.mapper;
import cn.xiegongzi.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface IUserMapper {
@Select("select * from user")
List<User> findAllUser();
}
編寫service介面和實現類
編寫controller
package cn.xiegongzi.controller;
import cn.xiegongzi.service.IUserService;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
/*
@Api
表示當前類可以被生成一個swagger文件
可以跟引數tags,引數表示:這整個介面的名字( 前端是介面,後端是controller控制層 )
*/
@Api(tags = "使用者管理介面集")
public class UserController {
@Autowired
private IUserService userService;
/*
@ApiImplicitParam 這個註解是對請求引數做限制用的,如:請求時要求前臺傳遞一個id,那麼:在這個註解裡面:就可以宣告這個引數的型別( 物件型別中要求屬性限制,可以使用@ApiModelProperty 也可以使用 符合jsr303規範的資料檢驗方式 )
*/
// 遵循restful風格 要是使用@RequestMapping的話,會生成多個介面swagger文件( 即:對應post、get.... )
@GetMapping("/swaggger/doc")
// value這個介面的名字;notes 對這個介面的描述
@ApiOperation(value = "獲取全部使用者介面" , notes = "獲取全部的使用者")
public String findAllUser() {
return JSON.toJSONString( userService.findAllUser() );
}
}
測試
13.2、結語
- 以上的內容是入門,其他的註解開發時自行摸索吧!
- 還有一種,比swagger更好,就是:ApiPost / ApiPost / eolink,自行下載安裝包,安裝之後玩一下
14、整合JPA
資料庫表字段資訊
匯入依賴
<!--匯入jpa需要的依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--專案需要的依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
編寫yml檔案
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
username: root
password: "072413"
jpa:
# 這裡可以不用hibernate,還可以用hikari( 這個在前面整合jdbc時見過,就是當時輸出的那句話 )
hibernate:
# 指定為update,每次啟動專案檢測表結構有變化的時候會新增欄位,表不存在時會新建表
ddl-auto: update
# 如果指定create,則每次啟動專案都會清空資料並刪除表,再新建
# 這裡面還可以跟:create-drop/create/none
naming:
# 指定jpa的自動錶生成策略,駝峰自動對映為下劃線格式
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl # 預設就是這個
# physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# 注掉的這種是:不用駝峰名字,直接把實體類的大寫字母變小寫就完了
show-sql: true # 在控制檯顯示sql語句( 不是真的sql語句,而是相當於:說明 ),預設是false
# 使用INNODB引擎
properties.hibernate.dialect: org.hibernate.dialect.MySQL55Dialect
database-platform: org.hibernate.dialect.MySQL55Dialect
# 使用JPA建立表時,預設使用的儲存引擎是MyISAM,通過指定資料庫版本,可以使用InnoDB
編寫實體類
package cn.xiegongzi.entity;
import lombok.Data;
import org.springframework.data.annotation.Id;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import java.io.Serializable;
@Data
// @AllArgsConstructor
// @NoArgsConstructor
@Entity
/** @Entity
* 表明:當前類和資料庫中的這個同類名的資料庫表形成ORM對映關係
* 要是資料庫中沒有這個表,那麼:根據yml配置的ddl-auto: update 就會自動幫我們生成
*/
public class ZiXieQing implements Serializable {
@javax.persistence.Id
@Id // 表明這個屬性是資料庫表中的主鍵
@GeneratedValue(strategy = GenerationType.IDENTITY) // 表示:自增 預設是auto,即:和資料庫中的auto_increment是一樣的
private int id;
@Column( length = 15 ) // 生成資料庫中的列欄位,裡面的引數不止這些,還可以用其他的,對應資料庫列欄位的那些操作
// 可以點進原始碼看一下
private String name;
// public ZiXieQing() {
// }
public ZiXieQing(int id, String name) {
this.id = id;
this.name = name;
}
}
附:@Column
註解中可以支援的屬性
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String name() default "";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0;
int scale() default 0;
}
編寫mapper
package cn.xiegongzi.mapper;
import cn.xiegongzi.entity.ZiXieQing;
import org.springframework.data.jpa.repository.JpaRepository;
@Repository
/**
* 注:這裡別用@Mapper這個註解,因為:@mapper是mybatis提供的註解
* JpaRepository相對mybatis來說就是是外部的東西。因此:並不能支援@mapper註解
*/
public interface ZiXieQingMapper extends JpaRepository<ZiXieQing , Integer> {
/*
JpaRepository這裡面有預設的一些方法,即:增刪查改...
JpaRepository<ZiXieQing , Integer> 本來樣子是:JpaRepository<T , ID>
T 表示:自己編寫的實體類 型別
ID 表示: 實體類中id欄位的型別 注:本示例中,實體類中id是int 因為要弄自增就必須為int,不然和資料庫對映時對不上
*/
}
附:JpaRepository
中提供的方法
編寫service介面和實現類
編寫controller
測試
現在去看一下資料庫
生成出來了,完成
15、整合mybatis-plus
- mybatis-plus官網地址:https://baomidou.com/guide/
匯入依賴
<!--mybatis-plus需要的依賴-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
編寫yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
username: root
password: "072413"
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # mybatis-plus配置日誌
map-underscore-to-camel-case: true # 開啟駝峰對映 即:實體類屬性名和資料庫欄位採用駝峰對映
auto-mapping-behavior: full # 自動對映欄位
mapper-locations: classpath:mapper/*.xml # 如果使用了mybatis和mybatis-plus 那麼這裡就可以把mybatis的實現類xml整合進來
# 但是:最好別這種做,用了mybatis就別用mybatis-plus,二者最好只用其一
注:別把mybatis和mybatis-plus一起整合到spring中,否則:很容易出問題,雖然:mybatis-plus是mybatis的增強版,既然是增強版,那麼就不會拋棄它原有的東西,只會保留原有的東西,然後新增功能,但是:mybatis和mybatis-plus整合到一起之後很容易造成版本衝突,因此:對於單個系統模組 / 單個系統來說,建議二者只選其一整合 ( PS:當然事情不是絕對的 我說的是萬一,只是操作不當很容易觸發錯誤而已,但是:二者一起整合也是可以的,當出現報錯時可以解決掉,不延伸了 ,這不是這裡該做的事情 )
編寫實體類
package cn.xiegongzi.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "user") // 表名註解
public class User implements Serializable {
@TableId(type = IdType.AUTO) // 表示主鍵,這個主鍵是一個Long型別的值( 即:snowflake雪花演算法 )
private Integer id;
@TableField("username") // 資料庫欄位名 就是:當實體類中的欄位和資料庫欄位不一樣時可以使用
private String name;
private String phone;
}
編寫mapper
package cn.xiegongzi.mapper;
import cn.xiegongzi.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper // 不想在每個mapper層都寫這個註解,那把@MapperScan("cn.xiegongzi.mapper") 在啟動類中加入這個註解也可以實現
public interface IUserMapper extends BaseMapper<User> {
/*
BaseMapper 和 JPA一樣,內部有很多方法 , 即:CRUD.....,還有分頁( 分頁就是page()這個方法 )
BaseMapper原來的樣子是:BaseMapper<T> T表示實體類 型別
*/
}
附:BaseMapper<T>
提供的方法如下:
測試
其他的知識,在mybatis-plus官網中都有
15、分散式本地快取技術ehcache
- 還有一種比較流行的是
Caffeine
這個東西要更簡單一點,而且不需要藉助xml檔案,而ehcache需要藉助xml檔案
15.1、Ehcache介紹
- Ehacahe是一個比較成熟的Java快取框架,最早從hibernate發展而來,是程序中的快取系統,它提供了用記憶體、磁碟檔案儲存,以及分散式儲存方式等多種靈活的cache管理方案
15.2、ehcache常用註解
15.2.1、@CacheConfig註解
- 用於標準在類上,可以存放該類中所有快取的公有屬性( 如:設定快取名字 )
@CacheConfig(cacheNames = "users")
public class UserService{
}
- 當然:這個註解其實可以使用
@Cacheable
來代替
15.2.2、@Cacheable註解( 讀資料時 ) - 用得最多
- 應用到讀取資料的方法上,如:查詢資料的方法,使用了之後可以做到先從本地快取中讀取資料,若是沒有在呼叫此註解下的方法去資料庫中讀取資料,當然:還可以將資料庫中讀取的資料放到用此註解配置的指定快取中
@Cacheable(value = "user", key = "#userId")
User selectUserById( Integer userId );
@Cacheable註解的屬性
-
value、cacheNames
- 這兩個引數其實是等同的( cacheNames為Spring 4新增的,作為value的別名 )
- 這兩個屬性的作用:用於指定快取儲存的集合名
-
key
- 作用:快取物件儲存在Map集合中的key值
-
condition
- 作用:快取物件的條件,即:只有滿足這裡面配置的表示式條件的內容才會被快取,如:@Cache( key = "#userId",condition="#userId.length() < 3" 這個表示式表示只有當userId長度小於3的時候才會被快取
-
unless
- 作用:另外一個快取條件,它不同於condition引數的地方在於此屬性的判斷時機( 此註解中編寫的條件時在函式被
呼叫之後
才做判斷,所以:這個屬性可以通過封裝的result進行判斷 )
- 作用:另外一個快取條件,它不同於condition引數的地方在於此屬性的判斷時機( 此註解中編寫的條件時在函式被
-
keyGenerator
- 作用:用於指定key生成器,若需要繫結一個自定義的key生成器,我們需要去實現
org.springframewart.cahce.intercceptor.KeyGenerator
介面,並使用改引數來繫結 - 注意點:改引數與上面的key屬性是互斥的
- 作用:用於指定key生成器,若需要繫結一個自定義的key生成器,我們需要去實現
-
cacheManager
- 作用:指定使用哪個快取管理器,也就是當有多個快取器時才需要使用
-
cacheResolver
- 作用:指定使用哪個快取解析器
- 需要通過
org.springframewaork.cache.interceptor.CacheResolver
介面來實現自己的快取解析器
15.2.3、@CachePut註解 ( 寫資料時 )
- 用在
寫資料
的方法上,如:新增 / 修改方法,呼叫方法時會自動把對應的資料放入快取,@CachePut
的引數和@Cacheable
差不多
@CachePut(value="user", key = "#userId")
public User save(User user) {
users.add(user);
return user;
}
15.2.4、@CacheEvict註解 ( 刪除資料時 )
- 用在刪除資料的方法上,呼叫方法時會從快取中移除相應的資料
@CacheEvict(value = "user", key = "#userId")
void delete( Integer userId);
-
這個註解除了和
@Cacheable
一樣的引數之外,還有另外兩個引數:- allEntries:預設為false,當為true時,會移除快取中該註解該屬性所在的方法的所有資料
- beforeInvocation:預設為false,在呼叫方法之後移除資料,當為true時,會在呼叫方法之前移除資料
15.2.5、@Cacheing組合註解 - 推薦
@Caching(
put = {
@CachePut(value = "user", key = "#userId"),
@CachePut(value = "user", key = "#username"),
@CachePut(value = "user", key = "#userAge"),
}
)
- 指的是:將userId、username、userAge放到名為user的快取中存起來
15.3、SpringBoot整合Ehcache
15.3.1、配置Ehache
依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
在
application.yml
配置檔案中加入配置
cache:
ehcache:
# 配置ehcache.xml配置檔案所在地
config: class:ehcache.xml
在主啟動類開啟快取功能
@SpringBootAllication
@EnableCaching
public class Starter {
public static void main(String[] args) {
SpringApplication.run(Starter.class);
}
}
編寫
ehcache.xml
配置檔案
- 在
resources
目錄下新建rhcache.xml
,並編寫如下內容:
<ehcache name="myCache">
<!--快取磁碟儲存路徑-->
<diskStore path = "D:/test/cache"/>
<!--預設的快取配置
maxElementsInMemory 快取最大數目
eternal 物件是否永久有效 一旦設定了,那麼timeout將不再起作用
timeToIdleSeconds 設定物件在實效前能允許的閒置時間( 單位:秒 ),預設值是0,即:可閒置時間無窮大
僅當eternal=“false"物件不是永久有效時使用
timeToLiveSeconds 設定物件失效前能允許的存活時間( 單位:秒 )
最大時間介於建立時間和失效時間之間
maxElementsOnDisk 磁碟最大快取個數
diskExpiryThreadIntervalSeconds 磁碟失效時,執行緒執行時間間隔,預設是120秒
memoryStoreEvictionPolicy 當達到設定的maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體
預設策略是LRU( 即:最近最少使用策略 )
還可以設定的策略:
FIFO 先進先出策略
LFU 最近最少被訪問策略
LRU 最近最少使用策略
快取的元素有一個時間戳,當快取容量滿了,同時又需要騰出地方來快取新的元素時,
那麼現有快取元素中的時間戳 離 當前時間最遠的元素將被清出快取
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
<!--下面的配置是自定義快取配置,可以複製貼上,用多套
name 起的快取名
overflowToDisk 當系統宕機時,資料是否儲存到上面配置的<diskStore path = "D:/test/cache"/>磁碟中
diskPersistent 是否快取虛擬機器重啟期資料
另外的配置項:
clearOnFlush 記憶體數量最大時是否清除
diskSpoolBufferSizeMB 設定diskStore( 即:磁碟快取 )的緩衝區大小,預設是30MB
每個Cache都應該有自己的一個緩衝區
-->
<cache
name="users"
eternal="false"
maxElementsInMemory="100"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
15.3.2、在專案中使用ehcahe
- 使用常用的
@Cacheable
註解舉例
查詢條件是單個時( service實現類中直接開註解 )
// 這裡的value值就是前面xml中配置的哪個cache name值
@Cacheable(value="users", key = "#username")
public User queryUserByUsername(String username) {
return userMapper.selectUserByUsername(username);
}
查詢條件是多個時( service實現類中直接開註解 )
- 本質:字串的拼接
// 這裡的UserDAO.username+就是封裝的UserDAO,裡面的屬性有username、userage、userPhone
@Cache(value="users", key = "#UserDAO.username+'-'+#UserDAO.userage+'-'+#UserDAO.userPhone")
public User queryUserByUsername(UserDAO userDAO) {
return userMapper.selectUserByUserDAO(userDAO);
}
其他的註解也都是差不多的
16、定時任務
16.1、小頂堆資料結構
- 就是一個完全二叉樹,同時這個二叉樹遵循一個規則,根節點存的值永遠小於兩個子節點存的值
- 樹結構只是一種邏輯結構,因此:資料還是要存起來的,而這種小頂堆就是採用了陣列
-
即:陣列下標為0的位置不放值,然後把樹結構的資料放在對應位置
- 樹結構資料轉成陣列資料的規律:從上到下、從左到右 - 即:根節點、左孩子節點、右孩子節點( 對照上面兩個圖來看 )
- 這種儲存方式找父節點也好找,就是陣列中( 當前數值的下標值 % 2 ) ,這種演算法的原理:就是利用二叉樹的深度 和 存放資料個數的關係( 數列 ),即:頂層最多可以放多少個數據?2的0次方;第二層最多可以存放多少個數據?2的1次方...........
-
這種小頂堆需要明白三個點:
- 存資料的方式: 上述提到了
- 取資料的方式:從底向上。即:從最底層開始,若比找尋的值小,那就找父節點,父節點也比所找尋數值小,繼續找父節點的父節點.,要是比父節點大,那就找相鄰兄弟節點嘛.........依次類推,最後就可以找到所找尋的資料了
- 存資料的方式:自底向上、逐漸上浮。即:從最底層開始,存的值 和 父節點相比,比父節點小的話,那存的值就是父節點存的值進行換位.....以此類推
16.2、時間輪演算法
16.3、基礎型時間輪
- 模仿時鐘,24個刻度( 陣列,每一個刻度作為陣列的下標 ),每一個刻度後面就是一個連結串列,這個連結串列中放對應的定時任務,到了指定時間點就把後面連結串列中的任務全部遍歷出來執行
- 缺點:當要弄年、月、秒這種就又要再加輪子,這樣就很複雜了,因此:此種方式只適合記一天24小時的定時任務,涉及到年月秒就不行了
16.4、round型時間輪
- 在前面基礎型時間輪的基礎上,在每一個刻度的位置再加一個round值( 每個刻度後面還是一個連結串列存定時任務 ),round值記錄的就是實際需求的值,如:一週,那round值就為7,當然這個round值可以是1,也可以是30....,每一次遍歷時鐘陣列的那24個刻度時,遍歷到某一個刻度,那麼就讓round值減1,知道round值為0時,就表示24陣列中當前這個刻度存的定時任務該執行了
- 缺點:需要讓round值減1,那麼就是需要對時間輪進行遍歷,如:定時任務應該是4號執行,但是3號遍歷時間輪時,定時任務並不執行,而此時也需要遍歷時間輪從而讓round值減1,這浪費了效能
16.5、分量時間輪
-
後續的定時任務框架就是基於這個做的,如:Spring中有一個@Scheduleed( cron = "x x x x ...." )註解,它的這個cron時間表達式就是基於這種分量時間輪
-
使用多個輪子
- 如:一個時間輪記錄小時0 - 24,而另一個輪子記錄天數0 - 30天
- 先遍歷天倫中的刻度,若今天是0 -30中要執行定時任務的那一天,那麼天輪的刻度指向的就是時輪
- 然後再去遍歷時輪中對應的那個刻度,從而找到這個刻度後面的連結串列,將連結串列遍歷出來,執行定時任務
16.6、Timer定時任務
-
底層原理就是:小頂堆,只是它的底層用了一個taskQueue任務佇列來充當小頂堆中的哪個陣列,存取找的邏輯都是和小頂堆一樣的
-
有著弊端:
- schedule() API 真正的執行時間 取決上一個任務的結束時間 - 會出現:少執行了次數
- scheduleAtFixedRate() API 想要的是嚴格按照預設時間 12:00:00 12:00:02 12:00:04,但是最終結果是:執行時間會亂
- 底層調的是
run()
,也就是單執行緒 - 缺點:任務阻塞( 阻塞原因:任務超時 )
package com.tuling.timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) {
Timer t = new Timer();// 任務啟動
for (int i=0; i<2; i++){
TimerTask task = new FooTimerTask("foo"+i);
t.scheduleAtFixedRate(task,new Date(),2000);// 任務新增 10s 5次 4 3
// 預設的執行時間nextExecutorTime 12:00:00 12:00:02 12:00:04
// schedule 真正的執行時間 取決上一個任務的結束時間 ExecutorTime 03 05 08 丟任務(少執行了次數)
// scheduleAtFixedRate 嚴格按照預設時間 12:00:00 12:00:02 12:00:04(執行時間會亂)
// 單執行緒 任務阻塞 任務超時
}
}
}
class FooTimerTask extends TimerTask {
private String name;
public FooTimerTask(String name) {
this.name = name;
}
public void run() {
try {
System.out.println("name="+name+",startTime="+new Date());
Thread.sleep(3000);
System.out.println("name="+name+",endTime="+new Date());
// 因為是單執行緒,所以解決辦法:使用執行緒池執行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
16.7、定時任務執行緒池
-
底層原理就是timer + 執行緒池來做到的
-
如下的
Executors.newScheduledThreadPool(5);
建立執行緒池的方法在高併發情況下,最好別用
package com.tuling.pool;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduleThreadPoolTest {
public static void main(String[] args) {
// 這種執行緒池叫做垃圾 - 瞭解即可
// 缺點:允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i=0;i<2;i++){
scheduledThreadPool.scheduleAtFixedRate(new Task("task-"+i),0,2, TimeUnit.SECONDS);
}
}
}
class Task implements Runnable{
private String name;
public Task(String name) {
this.name = name;
}
public void run() {
try {
System.out.println("name="+name+",startTime="+new Date());
Thread.sleep(3000);
System.out.println("name="+name+",endTime="+new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
16.8、@Scheduled註解實現
- 這玩意兒是Spring提供的
- 缺點就是其定時時間不能動態更改,它適用於具有固定任務週期的任務
- 注意點:要在相應的程式碼中使用
@Scheduled
註解來進行任務配置,那麼就需要在主啟動類上加上@EnableScheduling // 開啟定時任務
註解
這個註解的幾個屬性
-
fixedRate 表示任務執行之間的時間間隔,具體是指兩次任務的開始時間間隔,即第二次任務開始
時,第一次任務可能還沒結束 -
fixedDelay 表示任務執行之間的時間間隔,具體是指本次任務結束到下次任務開始之間的時間間
隔 - initialDelay 表示首次任務啟動的延遲時間
- cron 表示式:秒 分 小時 日 月 周 年
cron表示式說明
- 上圖萬用字元含義
萬用字元 | 意義 |
---|---|
? |
表示不指定值,即不關心某個欄位的取值時使用 需要注意的是,月份中的日期和星期可能會起衝突,因此在配置時這兩個得有一個是 ?
|
* |
表示所有值,例如:在秒的欄位上設定 * ,表示每一秒都會觸發 |
, |
用來分開多個值,例如在周欄位上設定 "MON,WED,FRI" 表示週一,週三和週五觸發 |
- |
表示區間,例如在秒上設定 "10-12",表示 10,11,12秒都會觸發 |
/ |
用於遞增觸發,如在秒上面設定"5/15" 表示從5秒開始,每增15秒觸發(5,20,35,50) |
# |
序號(表示每月的第幾個周幾),例如在周欄位上設定"6#3"表示在每月的第三個週六,(用 在母親節和父親節再合適不過了) |
L |
表示最後的意思 在日欄位設定上,表示當月的最後一天(依據當前月份,如果是二月還會自動判斷是否是潤年 在周欄位上表示星期六,相當於"7"或"SAT"(注意週日算是第一天) 如果在"L"前加上數字,則表示該資料的最後一個。例如在周欄位上設定"6L"這樣的格式,則表 示"本月最後一個星期五" |
W |
表示離指定日期的最近工作日(週一至週五) 例如在日欄位上設定"15W",表示離每月15號最近的那個工作日觸發。如果15號正好是週六,則找最近的週五(14號)觸發, 如果15號是周未,則找最近的下週一(16號)觸發,如果15號正好在工作日(週一至週五),則就在該天觸發 如果指定格式為 "1W",它則表示每月1號往後最近的工作日觸發。如果1號正是週六,則將在3號下週一觸發。(注,"W"前只能設定具體的數字,不允許區間"-") |
L 和 W 組合 |
如果在日欄位上設定"LW",則表示在本月的最後一個工作日觸發(一般指發工資 ) |
周欄位的設定 |
若使用英文字母是不區分大小寫的 ,即 MON 與mon相同 |
cron表示式舉例
“0 0 12 * * ?” 每天中午12點觸發
“0 15 10 ? * *” 每天上午10:15觸發
“0 15 10 * * ?”
“0 15 10 * * ? *”
“0 15 10 * * ? 2005” 2005年的每天上午10:15 觸發
“0 0/5 14 * * ?” 在每天下午2點到下午2:55期間的每5分鐘觸發
“0 0-5 14 * * ?” 在每天下午2點到下午2:05期間的每1分鐘觸發
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44觸發
“0 15 10 ? * MON-FRI” 週一至週五的上午10:15觸發
“0 15 10 ? * 6L” 每月的最後一個星期五上午10:15觸發
“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最後一個星期五上午10:15觸發
“0 15 10 ? * 6#3” 每月的第三個星期五上午10:15觸發
0 23-7/2,8 * * * 晚上11點到早上8點之間每兩個小時,早上八點
0 11 4 * 1-3 每個月的4號和每個禮拜的禮拜一到禮拜三的早上11點
16.9、Redis實現 - 分散式定時任務
- 前面的方式都是單機的
16.8.1、zset實現
邏輯
- 將定時任務存放到 ZSet 集合中,並且將過期時間儲存到 ZSet 的 Score 欄位中
- 通過一個無線迴圈來判斷當前時間內是否有需要執行的定時任務,如果有則進行執行
import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;
public class DelayQueueExample {
// zset key
private static final String _KEY = "myTaskQueue";
public static void main(String[] args) throws InterruptedException {
Jedis jedis = JedisUtils.getJedis();
// 30s 後執行
long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
jedis.zadd(_KEY, delayTime, "order_1");
// 繼續新增測試資料
jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");
jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");
jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");
jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");
// 開啟定時任務佇列
doDelayQueue(jedis);
}
/**
* 定時任務佇列消費
* @param jedis Redis 客戶端
*/
public static void doDelayQueue(Jedis jedis) throws InterruptedException {
while (true) {
// 當前時間
Instant nowInstant = Instant.now();
long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒時間
long nowSecond = nowInstant.getEpochSecond();
// 查詢當前時間的所有任務
Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
for (String item : data) {
// 消費任務
System.out.println("消費:" + item);
}
// 刪除已經執行的任務
jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
Thread.sleep(1000); // 每秒查詢一次
}
}
}
16.8.2、鍵空間實現
邏輯
- 給所有的定時任務設定一個過期時間
- 等到了過期之後,我們通過訂閱過期訊息就能感知到定時任務需要被執行了,此時我們執行定時任務即可
- 注意點:預設情況下 Redis 是不開啟鍵空間通知的,需要我們通過
config set notify-keyspace-events Ex
的命令手動開啟,開啟之後定時任務的程式碼如下
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;
public class TaskExample {
public static final String _TOPIC = "__keyevent@0__:expired"; // 訂閱頻道名稱
public static void main(String[] args) {
Jedis jedis = JedisUtils.getJedis();
// 執行定時任務
doTask(jedis);
}
/**
* 訂閱過期訊息,執行定時任務
* @param jedis Redis 客戶端
*/
public static void doTask(Jedis jedis) {
// 訂閱過期訊息
jedis.psubscribe(new JedisPubSub() {
@Override
public void onPMessage(String pattern, String channel, String message) {
// 接收到訊息,執行定時任務
System.out.println("收到訊息:" + message);
}
}, _TOPIC);
}
}
16.8.Quartz任務排程
- 組成結構圖如下:需要時自行摸索即可
16.8.1.簡單示例
依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
定義job
public class MyJob implements Job {
private Logger log = LoggerFactory.getLogger(MyJob.class);
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey();
log.info("觸發器:{},所屬組:{},執行時間:{},執行任務:{}",
triggerKey.getName(),triggerKey.getGroup(),dateFormat.format(new Date()),"hello SpringBoot Quartz...");
}
}
編寫QuartzConfig
public class QuartzConfig {
@Bean
public JobDetail jobDetail() {
return JobBuilder.newJob(MyJob.class)
.storeDurably()
.build();
}
@Bean
public Trigger trigger01() {
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
// 每一秒執行一次
.withIntervalInSeconds(1)
// 永久重複,一直執行下去
.repeatForever();
return TriggerBuilder.newTrigger()
// 引數1、trigger名字;引數2、當前這個trigger所屬的組 - 參考時間輪儲存任務,那個刻度後面是怎麼存的任務
.withIdentity("trigger01", "group1")
.withSchedule(scheduleBuilder)
// 哪一個job,上一個方法中bean注入
.forJob("jobDetail")
.build();
}
/**
* 每兩秒觸發一次任務
*/
@Bean
public Trigger trigger02() {
return TriggerBuilder
.newTrigger()
.withIdentity("triiger02", "group1")
// cron時間表達式
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))
.forJob("jobDetail")
.build();
}
}
最後:SpringBoot還有很多內容,那些也就是整合各種框架而已,到了現在整合了這麼多,那也有點門路了,其實都差不多是通同樣的套路:1、引入SpringBoot整合的對應框架依賴;2、編寫對應的配置;3、使用對應的註解 / 編寫業務邏輯。差不多都是這樣的套路,因此:到時需要時直接面向百度程式設計即可
另外:SpringBoot原理篇連結:https://www.cnblogs.com/xiegongzi/p/15522405.html