SpringBoot_第二天
學(xué)習(xí)目標(biāo)
Mybatis整合&數(shù)據(jù)訪問
使用SpringBoot開發(fā)企業(yè)項(xiàng)目時(shí),持久層數(shù)據(jù)訪問是前端頁面數(shù)據(jù)展示的基礎(chǔ),SpringBoot支持市面上常見的關(guān)系庫產(chǎn)品(Oracle,Mysql,SqlServer,DB2等)對(duì)應(yīng)的相關(guān)持久層框架,當(dāng)然除了對(duì)于關(guān)系庫訪問的支持,也支持當(dāng)下眾多的非關(guān)系庫(Redis,Solr,MongoDB等)數(shù)據(jù)訪問操作,這里主要介紹SpringBoot集成Mybatis并實(shí)現(xiàn)持久層數(shù)據(jù)基本增刪改查操作。
SpringBoot 整合Mybatis
環(huán)境整合配置
Idea 下創(chuàng)建Maven 普通工程 springboot_mybatis
pom.xml 添加核心依賴
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
?
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
mybatis 集成
-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- springboot分頁插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
?
<!--mysql 驅(qū)動(dòng)-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
</dependencies>
?
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.yml 整合配置
## 端口號(hào)
server:
? port: 9999
?
## 數(shù)據(jù)源配置
spring:
? datasource:
?? type: com.mchange.v2.c3p0.ComboPooledDataSource
?? driver-class-name: com.mysql.cj.jdbc.Driver
?? url: jdbc:mysql://127.0.0.1:3306/springboot_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
?? username: root
?? password: root
?
## mybatis 配置
mybatis:
? mapper-locations: classpath:/mappers/*.xml
? type-aliases-package: com.xxxx.springboot.vo
? configuration:
## 下劃線轉(zhuǎn)駝峰配置
?? map-underscore-to-camel-case: true
?
## pageHelper
pagehelper:
? helper-dialect: mysql
#顯示dao 執(zhí)行sql語句
logging:
? level:
?? com:
? ?? xxxx:
? ? ?? springboot:
? ? ? ?? dao: debug
源代碼添加
Dao層接口方法定義
com.xxxx.springboot.dao 包下創(chuàng)建UserDao.java 接口聲明查詢方法
packagecom.xxxx.springboot.dao;
?
importcom.xxxx.springboot.vo.User;
?
publicinterfaceUserMapper{
? ? // 根據(jù)用戶名查詢用戶記錄
UserqueryUserByUserName(StringuserName);
}
SQL映射文件添加
resources/mappers 目錄下添加UserMapper.xml 配置查詢statetment
<?xmlversion="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mappernamespace="com.xxxx.springboot.dao.UserMapper">
<selectid="queryUserByUserName"parameterType="string"resultType="com.xxxx.springboot.vo.User">
? ? ?? select
? ? ?? id,user_name,user_pwd
? ? ?? from t_user
? ? ?? where user_name=#{userName}
</select>
</mapper>
添加service 、controller 對(duì)應(yīng)代碼
UserService.java
@Service
publicclassUserService{
@Autowired
privateUserMapperuserMapper;
?
publicUserqueryUserByUserName(StringuserName){
returnuserMapper.queryUserByUserName(userName);
?? }
}
UserController.java
@RestController
publicclassUserController{
?
@Resource
privateUserServiceuserService;
?
?
@GetMapping("user/{userName}")
publicUserqueryUserByUserName(@PathVariableStringuserName){
returnuserService.queryUserByUserName(userName);
?? }
}
添加應(yīng)用啟動(dòng)入口
@SpringBootApplication
@MapperScan("com.xxxx.springboot.dao")
publicclassStarter{
?
publicstaticvoidmain(String[]args) {
SpringApplication.run(Starter.class);
?? }
}
啟動(dòng)測(cè)試
運(yùn)行Starter? main方法,啟動(dòng)應(yīng)用瀏覽器測(cè)試查詢
后端日志打印效果:
SpringBoot數(shù)據(jù)訪問操作
完成SpringBoot 與Mybatis 集成后,接下來以用戶表為例實(shí)現(xiàn)一套用戶模塊基本數(shù)據(jù)維護(hù)。
接口方法 & Sql映射文件
UserDao 接口方法定義
UserDao 接口添加數(shù)據(jù)訪問基本方法
publicinterfaceUserMapper{
?
publicUserqueryById(Integerid);
?
UserqueryUserByUserName(StringuserName);
?
publicintsave(Useruser);
?
publicintupdate(Useruser);
?
publicList<User>selectByParams(UserQueryuserQuery);
?
}
UserMapper.xml 映射文件配置
<?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.xxxx.springboot.dao.UserMapper">
? ? <select id="queryById" parameterType="int" resultType="com.xxxx.springboot.vo.User">
? ? ? ? select *
? ? ? ? from t_user
? ? ? ? where id = #{id,jdbcType=INTEGER}
? ? </select>
? ? <select id="queryUserByUserName" parameterType="string" resultType="com.xxxx.springboot.vo.User">
? ? ? ? select *
? ? ? ? from t_user
? ? ? ? where user_name=#{userName}
? ? </select>
? ? <insert id="save" parameterType="com.xxxx.springboot.vo.User" useGeneratedKeys="true" keyProperty="id">
? ? ? ? insert into t_user(id,user_name,user_pwd) values(#{id},#{userName},#{userPwd})
? ? </insert>
? ? <update id="update" parameterType="com.xxxx.springboot.vo.User">
? ? ? ? update t_user set user_name =#{userName},user_pwd=#{userPwd}
? ? ? ? where id = #{id}
? ? </update>
? ? <select id="selectByParams" parameterType="com.xxxx.springboot.query.UserQuery" resultType="com.xxxx.springboot.vo.User">
? ? ? ? select *
? ? ? ? from t_user
? ? ? ? <where>
? ? ? ? ? ? <if test="null !=userName and userName !=''">
? ? ? ? ? ? ? ? and user_name like concat('%',#{userName},'%')
? ? ? ? ? ? </if>
? ? ? ? </where>
? ? </select>
</mapper>
UserService.java方法實(shí)現(xiàn)
public User queryUserByUserName(String userName){
? ? return userMapper.queryUserByUserName(userName);
}
public User queryUserByUserId(Integer userId){
? ? return userMapper.queryById(userId);
}
public void saveUser(User user) {
? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用戶名不能為空!");
? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用戶密碼不能為空!");
? ? User temp = userMapper.queryUserByUserName(user.getUserName());
? ? AssertUtil.isTrue(null != temp, "該用戶已存在!");
? ? AssertUtil.isTrue(userMapper.save(user)<1,"用戶記錄添加失敗!");
}
public void updateUser(User user) {
? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用戶名不能為空!");
? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用戶密碼不能為空!");
? ? User temp = userMapper.queryUserByUserName(user.getUserName());
? ? AssertUtil.isTrue(null != temp && !(temp.getId().equals(user.getId())), "該用戶已存在!");
? ? AssertUtil.isTrue(userMapper.update(user)<1,"用戶記錄添加失敗!");
}
public? void deleteUser(Integer id){
? ? AssertUtil.isTrue(null == id || null ==userMapper.queryById(id),"待刪除記錄不存在!");
? ? AssertUtil.isTrue(userMapper.delete(id)<1,"用戶刪除失敗!");
}
public PageInfo<User> queryUserByParams(UserQuery userQuery){
? ? PageHelper.startPage(userQuery.getPageNum(),userQuery.getPageSize());
? ? return new PageInfo<User>(userMapper.selectByParams(userQuery));
}
UserController.java 接口方法
@GetMapping("user/{userId}")
public User queryUserByUserId(@PathVariable? Integer userId){
? ? return userService.queryUserByUserId(userId);
}
@GetMapping("user/list")
public PageInfo<User> list(UserQuery userQuery){
? ? return userService.queryUserByParams(userQuery);
}
@PutMapping("user")
public ResultInfo saveUser(User user){
? ? ResultInfo resultInfo=new ResultInfo();
? ? try {
? ? ? ? userService.saveUser(user);
? ? } catch (ParamsException e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(e.getCode());
? ? ? ? resultInfo.setMsg(e.getMsg());
? ? }catch (Exception e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(300);
? ? ? ? resultInfo.setMsg("記錄添加失敗!");
? ? }
? ? return resultInfo;
}
@PostMapping("user")
public ResultInfo updateUser(User user){
? ? ResultInfo resultInfo=new ResultInfo();
? ? try {
? ? ? ? userService.updateUser(user);
? ? } catch (ParamsException e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(e.getCode());
? ? ? ? resultInfo.setMsg(e.getMsg());
? ? }catch (Exception e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(300);
? ? ? ? resultInfo.setMsg("記錄更新失敗!");
? ? }
? ? return resultInfo;
}
@DeleteMapping("user/{userId}")
public ResultInfo deleteUser(@PathVariable? Integer? userId){
? ? ResultInfo resultInfo=new ResultInfo();
? ? try {
? ? ? ? userService.deleteUser(userId);
? ? } catch (ParamsException e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(e.getCode());
? ? ? ? resultInfo.setMsg(e.getMsg());
? ? }catch (Exception e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(300);
? ? ? ? resultInfo.setMsg("記錄刪除失敗!");
? ? }
? ? return resultInfo;
}
PostMan 接口測(cè)試工具下載與使用
在企業(yè)web 應(yīng)用開發(fā)中,對(duì)服務(wù)器端接口進(jìn)行測(cè)試,通常借助接口測(cè)試工具,這里使用Postman 接口測(cè)試工具來對(duì)后臺(tái)restful接口進(jìn)行測(cè)試,Postman 工具下載地址: https://www.getpostman.com/apps 選中對(duì)應(yīng)平臺(tái)下載即可。
下載安裝后,啟動(dòng)Postman 根據(jù)后臺(tái)接口地址發(fā)送響應(yīng)請(qǐng)求即可對(duì)接口進(jìn)行測(cè)試。
API 文檔構(gòu)建工具-Swagger2
由于Spring Boot能夠快速開發(fā)、便捷部署等特性,通常在使用Spring Boot構(gòu)建Restful 接口應(yīng)用時(shí)考慮到多終端的原因,這些終端會(huì)共用很多底層業(yè)務(wù)邏輯,因此我們會(huì)抽象出這樣一層來同時(shí)服務(wù)于多個(gè)移動(dòng)端或者Web前端。對(duì)于不同的終端公用一套接口API時(shí)對(duì)于聯(lián)調(diào)測(cè)試時(shí)就需要知道后端提供的接口Api 列表文檔,對(duì)于服務(wù)端開發(fā)人員來說就需要編寫接口文檔,描述接口調(diào)用地址參數(shù)結(jié)果等,這里借助第三方構(gòu)建工具Swagger2來實(shí)現(xiàn)Api文檔生成功能。
環(huán)境整合配置
pom.xml 依賴添加
<dependency>
? <groupId>io.springfox</groupId>
? <artifactId>springfox-swagger2</artifactId>
? <version>2.9.2</version>
</dependency>
<dependency>
? <groupId>io.springfox</groupId>
? <artifactId>springfox-swagger-ui</artifactId>
? <version>2.9.2</version>
</dependency>
配置類添加
@Configuration
@EnableSwagger2
public class Swagger2 {
? ? @Bean
? ? public Docket createRestApi() {
? ? ? ? return new Docket(DocumentationType.SWAGGER_2)
? ? ? ? ? ? ? ? .apiInfo(apiInfo())
? ? ? ? ? ? ? ? .select()
? ? ? ? ? ? ? ? .apis(RequestHandlerSelectors.basePackage("com.xxxx.springboot.controller"))
? ? ? ? ? ? ? ? .paths(PathSelectors.any())
? ? ? ? ? ? ? ? .build();
? ? }
? ? private ApiInfo apiInfo() {
? ? ? ? return new ApiInfoBuilder()
? ? ? ? ? ? ? ? .title("用戶管理接口API文檔參考")
? ? ? ? ? ? ? ? .version("1.0")
? ? ? ? ? ? ? ? .build();
? ? }
}
Swagger2 常用注解說明
@Api
@Api:用在請(qǐng)求的類上,說明該類的作用
? ? tags="說明該類的作用"
@Api(tags="APP用戶注冊(cè)Controller")
@ApiOperation
@ApiOperation:"用在請(qǐng)求的方法上,說明方法的作用"
? ? value="說明方法的作用"
? ? notes="方法的備注說明"
@ApiOperation(value="用戶注冊(cè)",notes="手機(jī)號(hào)、密碼都是必輸項(xiàng),年齡隨邊填,但必須是數(shù)字")
@ApiImplicitParams
@ApiImplicitParams:用在請(qǐng)求的方法上,包含一組參數(shù)說明
? ? @ApiImplicitParam:用在 @ApiImplicitParams 注解中,指定一個(gè)請(qǐng)求參數(shù)的配置信息? ? ?
? ? ? ? name:參數(shù)名
? ? ? ? value:參數(shù)的漢字說明、解釋
? ? ? ? required:參數(shù)是否必須傳
? ? ? ? paramType:參數(shù)放在哪個(gè)地方
? ? ? ? ? ? · header --> 請(qǐng)求參數(shù)的獲?。篅RequestHeader
? ? ? ? ? ? · query --> 請(qǐng)求參數(shù)的獲?。篅RequestParam
? ? ? ? ? ? · path(用于restful接口)--> 請(qǐng)求參數(shù)的獲?。篅PathVariable
? ? ? ? ? ? · body(不常用)
? ? ? ? ? ? · form(不常用)? ?
? ? ? ? dataType:參數(shù)類型,默認(rèn)String,其它值dataType="Integer"? ? ?
? ? ? ? defaultValue:參數(shù)的默認(rèn)值
@ApiImplicitParams({
? ? @ApiImplicitParam(name="mobile",value="手機(jī)號(hào)",required=true,paramType="form"),
? ? @ApiImplicitParam(name="password",value="密碼",required=true,paramType="form"),
? ? @ApiImplicitParam(name="age",value="年齡",required=true,paramType="form",dataType="Integer")
})
@ApiResponses
@ApiResponses:用于請(qǐng)求的方法上,表示一組響應(yīng)
? ? @ApiResponse:用在@ApiResponses中,一般用于表達(dá)一個(gè)錯(cuò)誤的響應(yīng)信息
? ? ? ? code:數(shù)字,例如400
? ? ? ? message:信息,例如"請(qǐng)求參數(shù)沒填好"
? ? ? ? response:拋出異常的類
@ApiOperation(value = "select請(qǐng)求",notes = "多個(gè)參數(shù),多種的查詢參數(shù)類型")
@ApiResponses({
? ? @ApiResponse(code=400,message="請(qǐng)求參數(shù)沒填好"),
? ? @ApiResponse(code=404,message="請(qǐng)求路徑?jīng)]有或頁面跳轉(zhuǎn)路徑不對(duì)")
})
@ApiModel
@ApiModel:用于響應(yīng)類上,表示一個(gè)返回響應(yīng)數(shù)據(jù)的信息
? ? ? ? ? ? (這種一般用在post創(chuàng)建的時(shí)候,使用@RequestBody這樣的場(chǎng)景,
? ? ? ? ? ? 請(qǐng)求參數(shù)無法使用@ApiImplicitParam注解進(jìn)行描述的時(shí)候)
? ? @ApiModelProperty:用在屬性上,描述響應(yīng)類的屬性
@ApiModel(description= "返回響應(yīng)數(shù)據(jù)")
public class RestMessage implements Serializable{
? ? @ApiModelProperty(value = "是否成功")
? ? private boolean success=true;
? ? @ApiModelProperty(value = "返回對(duì)象")
? ? private Object data;
? ? @ApiModelProperty(value = "錯(cuò)誤編號(hào)")
? ? private Integer errCode;
? ? @ApiModelProperty(value = "錯(cuò)誤信息")
? ? private String message;
? ? /* getter/setter */
}
用戶模塊注解配置
UserController.java 接口方法注解使用
@GetMapping("user/uname/{userName}")
@ApiOperation(value = "根據(jù)用戶名查詢用戶記錄")
@ApiImplicitParam(name = "userName",value = "查詢參數(shù)",required = true,paramType = "path")
public User queryUserByUserName(@PathVariable String userName){
? ? return userService.queryUserByUserName(userName);
}
@ApiOperation(value = "根據(jù)用戶id查詢用戶記錄")
@ApiImplicitParam(name = "userId",value = "查詢參數(shù)",required = true,paramType = "path")
@GetMapping("user/{userId}")
public User queryUserByUserId(@PathVariable? Integer userId, HttpServletRequest request){
? ? return userService.queryUserByUserId(userId);
}
@GetMapping("user/list")
@ApiOperation(value = "多條件查詢用戶列表記錄")
public PageInfo<User> list(UserQuery userQuery){
? ? return userService.queryUserByParams(userQuery);
}
@PutMapping("user")
@ApiOperation(value = "用戶添加")
@ApiImplicitParam(name = "user",value = "用戶實(shí)體類",dataType = "User")
public ResultInfo saveUser(@RequestBody? User user){
? ? ResultInfo resultInfo=new ResultInfo();
? ? try {
? ? ? ? userService.saveUser(user);
? ? } catch (ParamsException e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(e.getCode());
? ? ? ? resultInfo.setMsg(e.getMsg());
? ? }catch (Exception e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(300);
? ? ? ? resultInfo.setMsg("記錄添加失敗!");
? ? }
? ? return resultInfo;
}
@PostMapping("user")
@ApiOperation(value = "用戶更新")
@ApiImplicitParam(name = "user",value = "用戶實(shí)體類",dataType = "User")
public ResultInfo updateUser(@RequestBody? User user){
? ? ResultInfo resultInfo=new ResultInfo();
? ? try {
? ? ? ? userService.updateUser(user);
? ? } catch (ParamsException e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(e.getCode());
? ? ? ? resultInfo.setMsg(e.getMsg());
? ? }catch (Exception e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(300);
? ? ? ? resultInfo.setMsg("記錄更新失敗!");
? ? }
? ? return resultInfo;
}
@PutMapping("user/{userId}")
@ApiOperation(value = "根據(jù)用戶id刪除用戶記錄")
@ApiImplicitParam(name = "userId",value = "查詢參數(shù)",required = true,paramType = "path")
public ResultInfo deleteUser(@PathVariable? Integer? userId){
? ? ResultInfo resultInfo=new ResultInfo();
? ? try {
? ? ? ? userService.deleteUser(userId);
? ? } catch (ParamsException e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(e.getCode());
? ? ? ? resultInfo.setMsg(e.getMsg());
? ? }catch (Exception e) {
? ? ? ? e.printStackTrace();
? ? ? ? resultInfo.setCode(300);
? ? ? ? resultInfo.setMsg("記錄刪除失敗!");
? ? }
? ? return resultInfo;
}
JavaBean 使用
User.java
@ApiModel(description = "響應(yīng)結(jié)果-用戶信息")
public class User {
? ? @ApiModelProperty(value = "用戶id",example = "0")
? ? private Integer id;
? ? @ApiModelProperty(value = "用戶名")
? ? private String userName;
? ? @ApiModelProperty(value = "用戶密碼")
? ? private String userPwd;
? ? /*
? ? ? 省略get|set
? ? */
}
UserQuery.java
@ApiModel(description = "用戶模塊條件查詢類")
public class UserQuery {
? ? @ApiModelProperty(value = "分頁頁碼",example = "1")
? ? private Integer pageNum=1;
? ? @ApiModelProperty(value = "每頁大小",example = "10")
? ? private Integer pageSize=10;
? ? @ApiModelProperty(value = "用戶名")
? ? private String userName;
? ? /*
? ? ? 省略get|set
? ? */
}
ResultInfo.java
@ApiModel(description = "響應(yīng)結(jié)果-Model信息")
public class ResultInfo {
? ? @ApiModelProperty(value = "響應(yīng)狀態(tài)碼",example = "200")
? ? private Integer code=200;
? ? @ApiModelProperty(value = "響應(yīng)消息結(jié)果")
? ? private String msg="success";
? ? @ApiModelProperty(value = "響應(yīng)具體結(jié)果信息")
? ? private Object result;
? ? /*
? ? ? 省略get|set
? ? */
}
Swagger2 接口文檔訪問
啟動(dòng)工程,瀏覽器訪問:http://localhost:9999/swagger-ui.html
SpringBoot應(yīng)用熱部署
什么是熱部署?
熱部署,就是在應(yīng)用正在運(yùn)行的時(shí)候升級(jí)軟件(增加業(yè)務(wù)/修改bug),卻不需要重新啟動(dòng)應(yīng)用
大家都知道在項(xiàng)目開發(fā)過程中,常常會(huì)改動(dòng)頁面數(shù)據(jù)或者修改數(shù)據(jù)結(jié)構(gòu),為了顯示改動(dòng)效果,往往需要重啟應(yīng)用查看改變效果,其實(shí)就是重新編譯生成了新的 Class 文件,這個(gè)文件里記錄著和代碼等對(duì)應(yīng)的各種信息,然后 Class 文件將被虛擬機(jī)的 ClassLoader 加載。
而熱部署正是利用了這個(gè)特點(diǎn),它監(jiān)聽到如果有 Class 文件改動(dòng)了,就會(huì)創(chuàng)建一個(gè)新的 ClaassLoader 進(jìn)行加載該文件,經(jīng)過一系列的過程,最終將結(jié)果呈現(xiàn)在我們眼前,Spring Boot通過配置DevTools? 工具來達(dá)到熱部署效果。
在原理上是使用了兩個(gè)ClassLoader,一個(gè)Classloader加載那些不會(huì)改變的類(第三方Jar包),另一個(gè)ClassLoader加載會(huì)更改的類,稱為restart ClassLoader,這樣在有代碼更改的時(shí)候,原來的restart ClassLoader 被丟棄,重新創(chuàng)建一個(gè)restart ClassLoader,由于需要加載的類相比較少,所以實(shí)現(xiàn)了較快的重啟時(shí)間。
熱部署環(huán)境配置與測(cè)試
配置 DevTools 環(huán)境
修改 Pom 文件,添加 DevTools 依賴
<!-- DevTools 的坐標(biāo) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
? ? <!--當(dāng)前這個(gè)項(xiàng)目被繼承之后,這個(gè)不向下傳遞-->
<optional>true</optional>
</dependency>
同時(shí)在plugin中添加devtools生效標(biāo)志
<plugin>
? <groupId>org.springframework.boot</groupId>
? <artifactId>spring-boot-maven-plugin</artifactId>
? <configuration>
? ? ? <fork>true</fork><!-- 如果沒有該配置,熱部署的devtools不生效 -->
? </configuration>
</plugin>
devtools可以實(shí)現(xiàn)頁面熱部署(即頁面修改后會(huì)立即生效,這個(gè)可以直接在application.properties文件中配置spring.thymeleaf.cache=false來實(shí)現(xiàn)),實(shí)現(xiàn)類文件熱部署(類文件修改后不會(huì)立即生效),實(shí)現(xiàn)對(duì)屬性文件的熱部署。即devtools會(huì)監(jiān)聽classpath下的文件變動(dòng),并且會(huì)立即重啟應(yīng)用(發(fā)生在保存時(shí)機(jī)),注意:因?yàn)槠洳捎玫奶摂M機(jī)機(jī)制,該項(xiàng)重啟是很快的。?配置了后在修改java文件后也就支持了熱啟動(dòng),不過這種方式是屬于項(xiàng)目重啟(速度比較快的項(xiàng)目重啟),會(huì)清空session中的值,也就是如果有用戶登陸的話,項(xiàng)目重啟后需要重新登陸。
默認(rèn)情況下,/META-INF/maven,/META-INF/resources,/resources,/static,/templates,/public這些文件夾下的文件修改不會(huì)使應(yīng)用重啟,但是會(huì)重新加載(devtools內(nèi)嵌了一個(gè)LiveReload server,當(dāng)資源發(fā)生改變時(shí),瀏覽器刷新)
全局配置文件配置
在application.yml中配置spring.devtools.restart.enabled=false,此時(shí)restart類加載器還會(huì)初始化,但不會(huì)監(jiān)視文件更新。
spring:
? ## 熱部署配置
? devtools:
? ? restart:
? ? ? enabled: true
? ? ? # 設(shè)置重啟的目錄,添加目錄的文件需要restart
? ? ? additional-paths: src/main/java
? ? ? # 解決項(xiàng)目自動(dòng)重新編譯后接口報(bào)404的問題
? ? ? poll-interval: 3000
? ? ? quiet-period: 1000
Idea 配置
當(dāng)我們修改了Java類后,IDEA默認(rèn)是不自動(dòng)編譯的,而spring-boot-devtools又是監(jiān)測(cè)classpath下的文件發(fā)生變化才會(huì)重啟應(yīng)用,所以需要設(shè)置IDEA的自動(dòng)編譯
自動(dòng)編譯配置
File-Settings-Compiler-Build Project automatically
Registry 屬性修改
ctrl + shift + alt + /,選擇Registry,勾上 Compiler autoMake allow when app running
熱部署效果測(cè)試
第一次訪問 user/uname/{uname} 接口
@GetMapping("user/uname/{userName}")
@ApiOperation(value = "根據(jù)用戶名查詢用戶記錄")
@ApiImplicitParam(name = "userName",value = "查詢參數(shù)",required = true,paramType = "path")
public User queryUserByUserName(@PathVariable String userName){
? ? return userService.queryUserByUserName(userName);
}
修改接口代碼 控制臺(tái)打印接收的uname參數(shù) ctrl+f9 鍵重新編譯 瀏覽器訪問
@GetMapping("user/uname/{userName}")
@ApiOperation(value = "根據(jù)用戶名查詢用戶記錄")
@ApiImplicitParam(name = "userName",value = "查詢參數(shù)",required = true,paramType = "path")
public User queryUserByUserName(@PathVariable String userName){
? ? System.out.println("查詢參數(shù)-->userName:"+userName);
? ? return userService.queryUserByUserName(userName);
}
SpringBoot單元測(cè)試
做過web項(xiàng)目開發(fā)的對(duì)于單元測(cè)試都并不陌生了,通過它能夠快速檢測(cè)業(yè)務(wù)代碼功能的正確與否,SpringBoot框架對(duì)單元測(cè)試也提供了良好的支持,來看SpringBoot應(yīng)用中單元測(cè)試的使用。
pom.xml 測(cè)試依賴添加
<dependency>
? ? <groupId>org.springframework.boot</groupId>
? ? <artifactId>spring-boot-starter-test</artifactId>
</dependency>
Service業(yè)務(wù)方法測(cè)試
這里以UserService為例,src/tets/java 目錄下添加測(cè)試包 com.xxxx.sprinboot.service 定義測(cè)試類代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
public class TestUserService {
? ? private Logger log = LoggerFactory.getLogger(TestUserService.class);
? ? @Resource
? ? private UserService userService;
? ? @Before
? ? public void before(){
? ? ? ? log.info("單元測(cè)試開始...");
? ? }
? ? @Test
? ? public? void test01(){
? ? ? ? log.info(userService.queryUserByUserId(10).toString());
? ? }
? ? @Test
? ? public? void test02(){
? ? ? ? log.info(userService.queryUserByParams(new UserQuery()).toString());
? ? }
? ? @After
? ? public void after(){
? ? ? ? log.info("單元測(cè)試結(jié)束...");
? ? }
}
控制層接口方法測(cè)試
視圖層代碼使用MockMvc 進(jìn)行測(cè)試,這里以UserCntroller 為例,src/tets/java 目錄下添加測(cè)試包 com.xxxx.sprinboot.controller 定義測(cè)試類代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
@AutoConfigureMockMvc
public class TestUserController {
? ? private Logger log = LoggerFactory.getLogger(TestUserController.class);
? ? @Autowired
? ? private MockMvc mockMvc;
? ? //用戶列表查詢
? ? @Test
? ? public void apiTest01()throws Exception{
? ? ? ? MvcResult mvcResult=mockMvc.perform(MockMvcRequestBuilders.get("/user/list")).
? ? ? ? ? ? ? ? andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
? ? ? ? log.info("響應(yīng)狀態(tài):{}",mvcResult.getResponse().getStatus());
? ? ? ? log.info("響應(yīng)內(nèi)容:{}",mvcResult.getResponse().getContentAsString());;
? ? }
? ? // 用戶名記錄查詢
? ? @Test
? ? public void apiTest02()throws Exception{
? ? ? ? MvcResult mvcResult=mockMvc.perform(MockMvcRequestBuilders.get("/user/uname/admin")).
? ? ? ? ? ? ? ? andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
? ? ? ? log.info("響應(yīng)狀態(tài):{}",mvcResult.getResponse().getStatus());
? ? ? ? log.info("響應(yīng)內(nèi)容:{}",mvcResult.getResponse().getContentAsString());;
? ? }
}
分布式緩存Ehcache整合
EhCache是一個(gè)比較成熟的Java緩存框架,最早從hibernate發(fā)展而來, 是進(jìn)程中的緩存系統(tǒng),它提供了用內(nèi)存,磁盤文件存儲(chǔ),以及分布式存儲(chǔ)方式等多種靈活的cache管理方案,快速簡(jiǎn)單。
Spring Boot對(duì)Ehcache的使用提供支持,所以在Spring Boot中只需簡(jiǎn)單配置即可使用Ehcache實(shí)現(xiàn)數(shù)據(jù)緩存處理。
Spring Cache 相關(guān)注解說明
SpringBoot 內(nèi)部使用SpringCache 來實(shí)現(xiàn)緩存控制,這里集成Ehcache實(shí)際上是對(duì)SpringCache 抽象的其中一種實(shí)現(xiàn),這里在使用Ehcache實(shí)現(xiàn)緩存控制時(shí)相關(guān)注解說明如下
@CacheConfig
用于標(biāo)注在類上,可以存放該類中所有緩存的公有屬性,比如設(shè)置緩存的名字。
@CacheConfig(cacheNames = "users")
public class UserService {。。。}
這里也可以不使用該注解,直接使用@Cacheable配置緩存集的名字。
@Cacheable
應(yīng)用到讀取數(shù)據(jù)的方法上,即可緩存的方法,如查找方法,先從緩存中讀取,如果沒有再調(diào)用相應(yīng)方法獲取數(shù)據(jù),然后把數(shù)據(jù)添加到緩存中。
該注解主要有下面幾個(gè)參數(shù):
value、cacheNames:兩個(gè)等同的參數(shù)(cacheNames為Spring 4新增,作為value的別名),用于指定緩存存儲(chǔ)的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必須有的value屬性,也成為非必需項(xiàng)了
key:緩存對(duì)象存儲(chǔ)在Map集合中的key值,非必需,缺省按照函數(shù)的所有參數(shù)組合作為key值,若自己配置需使用SpEL表達(dá)式,比如:@Cacheable(key = "#p0"):使用函數(shù)第一個(gè)參數(shù)作為緩存的key值,更多關(guān)于SpEL表達(dá)式的詳細(xì)內(nèi)容可參考官方文檔
condition:緩存對(duì)象的條件,非必需,也需使用SpEL表達(dá)式,只有滿足表達(dá)式條件的內(nèi)容才會(huì)被緩存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有當(dāng)?shù)谝粋€(gè)參數(shù)的長(zhǎng)度小于3的時(shí)候才會(huì)被緩存。
unless:另外一個(gè)緩存條件參數(shù),非必需,需使用SpEL表達(dá)式。它不同于condition參數(shù)的地方在于它的判斷時(shí)機(jī),該條件是在函數(shù)被調(diào)用之后才做判斷的,所以它可以通過對(duì)result進(jìn)行判斷。
keyGenerator:用于指定key生成器,非必需。若需要指定一個(gè)自定義的key生成器,我們需要去實(shí)現(xiàn)org.springframework.cache.interceptor.KeyGenerator接口,并使用該參數(shù)來指定。需要注意的是:該參數(shù)與key是互斥的
cacheManager:用于指定使用哪個(gè)緩存管理器,非必需。只有當(dāng)有多個(gè)時(shí)才需要使用
cacheResolver:用于指定使用那個(gè)緩存解析器,非必需。需通過org.springframework.cache.interceptor.CacheResolver接口來實(shí)現(xiàn)自己的緩存解析器,并用該參數(shù)指定。
@Cacheable(value = "user", key = "#id")
User selectUserById(final Integer id);
@CachePut
應(yīng)用到寫數(shù)據(jù)的方法上,如新增/修改方法,調(diào)用方法時(shí)會(huì)自動(dòng)把相應(yīng)的數(shù)據(jù)放入緩存,@CachePut的參數(shù)與@Cacheable類似,示例如下:
@CachePut(value = "user", key = "#user.id")?
public User save(User user) {?
? ? users.add(user);?
? ? return user;?
}?
@CacheEvict
應(yīng)用到移除數(shù)據(jù)的方法上,如刪除方法,調(diào)用方法時(shí)會(huì)從緩存中移除相應(yīng)的數(shù)據(jù),示例如下:
@CacheEvict(value = "user", key = "#id")
void delete(final Integer id);
除了同@Cacheable一樣的參數(shù)之外,@CacheEvict還有下面兩個(gè)參數(shù):
allEntries:非必需,默認(rèn)為false。當(dāng)為true時(shí),會(huì)移除所有數(shù)據(jù)
beforeInvocation:非必需,默認(rèn)為false,會(huì)在調(diào)用方法之后移除數(shù)據(jù)。當(dāng)為true時(shí),會(huì)在調(diào)用方法之前移除數(shù)據(jù)。
@Caching
組合多個(gè)Cache注解使用。示例:
@Caching(
? ? put = {
? ? ? ? @CachePut(value = "user", key = "#user.id"),
? ? ? ? @CachePut(value = "user", key = "#user.username"),
? ? ? ? @CachePut(value = "user", key = "#user.age")
? }
}
將id-->user;username--->user;age--->user進(jìn)行緩存。
用戶管理模塊緩存引入
環(huán)境配置
pom.xml 依賴添加
<dependency>
? ? <groupId>org.springframework.boot</groupId>
? ? <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Ehcache 坐標(biāo) -->
<dependency>
? ? <groupId>net.sf.ehcache</groupId>
? ? <artifactId>ehcache</artifactId>
</dependency>
ehcahe.xml 文件添加
src/main/resources 目錄下添加ehcache.xml 文件,內(nèi)容如下:
<ehcache name="mycache">
? ? <diskStore path="C:\java\cache"/>
? ? <!--
? ? ? ? name:緩存名稱。
? ? ? ? maxElementsInMemory:緩存最大數(shù)目
? ? ? ? maxElementsOnDisk:硬盤最大緩存?zhèn)€數(shù)。
? ? ? ? eternal:對(duì)象是否永久有效,一但設(shè)置了,timeout將不起作用。
? ? ? ? overflowToDisk:是否保存到磁盤,當(dāng)系統(tǒng)宕機(jī)時(shí)
? ? ? ? timeToIdleSeconds:設(shè)置對(duì)象在失效前的允許閑置時(shí)間(單位:秒)。
? ? ? ? ? ? ? 僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,可選屬性,默認(rèn)值是0,也就是可閑置時(shí)間無窮大。
? ? ? ? timeToLiveSeconds:設(shè)置對(duì)象在失效前允許存活時(shí)間(單位:秒)。
? ? ? ? ? ? 最大時(shí)間介于創(chuàng)建時(shí)間和失效時(shí)間之間。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,默認(rèn)是0.,也就是對(duì)象存活時(shí)間無窮大。
? ? ? ? diskPersistent:是否緩存虛擬機(jī)重啟期數(shù)據(jù) Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
? ? ? ? diskSpoolBufferSizeMB:這個(gè)參數(shù)設(shè)置DiskStore(磁盤緩存)的緩存區(qū)大小。默認(rèn)是30MB。每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū)。
? ? ? ? diskExpiryThreadIntervalSeconds:磁盤失效線程運(yùn)行時(shí)間間隔,默認(rèn)是120秒。
? ? ? ? memoryStoreEvictionPolicy:當(dāng)達(dá)到maxElementsInMemory限制時(shí),Ehcache將會(huì)根據(jù)指定的策略去清理內(nèi)存。
? ? ? ? ? ? 默認(rèn)策略是LRU(最近最少使用)。你可以設(shè)置為FIFO(先進(jìn)先出)或是LFU(較少使用)。
? ? ? ? clearOnFlush:內(nèi)存數(shù)量最大時(shí)是否清除。
? ? ? ? memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認(rèn)策略)、FIFO(先進(jìn)先出)、LFU(最少訪問次數(shù))。
? ? ? ? ? ? FIFO,first in first out,這個(gè)是大家最熟的,先進(jìn)先出。
? ? ? ? ? ? LFU, Less Frequently Used,最近最少被訪問的。
? ? ? ? ? ? LRU,Least Recently Used,最近最少使用的,緩存的元素有一個(gè)時(shí)間戳,
? ? ? ? ? ? ? 當(dāng)緩存容量滿了,而又需要騰出地方來緩存新的元素的時(shí)候,那么現(xiàn)有緩存元素中時(shí)間戳離當(dāng)前時(shí)間最遠(yuǎn)的元素將被清出緩存。
? ? ? ? -->
? ? <defaultCache
? ? ? ? ? ? maxElementsInMemory="10000"
? ? ? ? ? ? eternal="false"
? ? ? ? ? ? timeToIdleSeconds="120"
? ? ? ? ? ? timeToLiveSeconds="120"
? ? ? ? ? ? maxElementsOnDisk="10000000"
? ? ? ? ? ? diskExpiryThreadIntervalSeconds="120"
? ? ? ? ? ? memoryStoreEvictionPolicy="LRU">
? ? </defaultCache>
? ? <cache
? ? ? ? ? ? name="users"
? ? ? ? ? ? eternal="false"
? ? ? ? ? ? maxElementsInMemory="100"
? ? ? ? ? ? overflowToDisk="false"
? ? ? ? ? ? diskPersistent="false"
? ? ? ? ? ? timeToIdleSeconds="0"
? ? ? ? ? ? timeToLiveSeconds="300"
? ? ? ? ? ? memoryStoreEvictionPolicy="LRU"/>
</ehcache>
application.yml 添加緩存配置
spring:
? datasource:
? ? type: com.mchange.v2.c3p0.ComboPooledDataSource
? ? driver-class-name: com.mysql.cj.jdbc.Driver
? ? url: jdbc:mysql://127.0.0.1:3306/springboot_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
? ? username: root
? ? password: root
? devtools:
? ? restart:
? ? ? enabled: true
? ? ? # 設(shè)置重啟的目錄,添加目錄的文件需要restart
? ? ? additional-paths: src/main/java
? ? ? # 解決項(xiàng)目自動(dòng)重新編譯后接口報(bào)404的問題
? ? ? poll-interval: 3000
? ? ? quiet-period: 1000
? cache:
? ? ehcache:
? ? ? config: classpath:ehcahe.xml
Starter 啟動(dòng)入口類啟動(dòng)緩存
@MapperScan("com.xxxx.springboot.dao")
@EnableCaching
@SpringBootApplication
public class Starter {
? ? public static void main(String[] args) {
? ? ? ? SpringApplication.run(Starter.class);
? ? }
}
緩存User 對(duì)象實(shí)現(xiàn)序列化接口
@ApiModel(description = "用戶實(shí)體對(duì)象")
public class User implements Serializable {
? ? @ApiModelProperty(value = "用戶id主鍵")
? ? private Integer id;
? ? @ApiModelProperty(value = "用戶名")
? ? private String userName;
? ? @ApiModelProperty(value = "用戶密碼")
? ? private String userPwd;
? ? /*
? ? ? 省略 get|set方法
? ? */
}
緩存代碼添加
這里以UserService 方法為例
用戶詳情查詢緩存添加
@Cacheable(value = "users",key = "#userId")
public User queryUserByUserId(Integer userId){
? ? return userMapper.queryById(userId);
}
用戶列表查詢緩存
@Cacheable(value = "users",key="#userQuery.userName+'-'+#userQuery.pageNum+'-'+#userQuery.pageSize")
public PageInfo<User> queryUserByParams(UserQuery userQuery){
? ? PageHelper.startPage(userQuery.getPageNum(),userQuery.getPageSize());
? ? return new PageInfo<User>(userMapper.selectByParams(userQuery));
}
用戶更新&刪除緩存清除
@Transactional(propagation = Propagation.REQUIRED)
@CacheEvict(value = "users",key="#user.id")
public void updateUser(User user) {
? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用戶名不能為空!");
? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用戶密碼不能為空!");
? ? User temp = userMapper.queryUserByUserName(user.getUserName());
? ? AssertUtil.isTrue(null != temp && !(temp.getId().equals(user.getId())), "該用戶已存在!");
? ? AssertUtil.isTrue(userMapper.update(user)<1,"用戶記錄添加失敗!");
}
@Transactional(propagation = Propagation.REQUIRED)
@CacheEvict(value = "users",allEntries=true)
public? void deleteUser(Integer userId){
? ? AssertUtil.isTrue(null == userId || null ==userMapper.queryById(userId),"待刪除記錄不存在!");
? ? AssertUtil.isTrue(userMapper.delete(userId)<1,"用戶刪除失敗!");
}
定時(shí)調(diào)度集成-Quartz
在日常項(xiàng)目運(yùn)行中,我們總會(huì)有需求在某一時(shí)間段周期性的執(zhí)行某個(gè)動(dòng)作。比如每天在某個(gè)時(shí)間段導(dǎo)出報(bào)表,或者每隔多久統(tǒng)計(jì)一次現(xiàn)在在線的用戶量等。
在Spring Boot中有Java自帶的java.util.Timer類,SpringBoot自帶的Scheduled來實(shí)現(xiàn),也有強(qiáng)大的調(diào)度器Quartz。Scheduled 在Spring3.X 引入,默認(rèn)SpringBoot自帶該功能,使用起來也很簡(jiǎn)單,在啟動(dòng)類級(jí)別添加@EnableScheduling注解即可引入定時(shí)任務(wù)環(huán)境。但遺憾的是Scheduled? 默認(rèn)不支持分布式環(huán)境,這里主要講解Quartz 時(shí)鐘調(diào)度框架與Spring Boot 集成。
環(huán)境整合配置
<dependency>
? ? <groupId>org.springframework.boot</groupId>
? ? <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
源代碼添加
定義job
com.xxxx.springboot下添加jobs包,定義待執(zhí)行job任務(wù)
public class MyFirstJob implements Job {
? ? private Logger log = LoggerFactory.getLogger(MyFirstJob.class);
? ? @Override
? ? public void execute(JobExecutionContext context) throws JobExecutionException {
? ? ? ? SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
? ? ? ? TriggerKey triggerKey =? context.getTrigger().getKey();
? ? ? ? log.info("觸發(fā)器:"+triggerKey.getName()+"-->所屬組:"+triggerKey.getGroup()+"-->"+sdf.format(new Date())+"-->"+"hello Spring Boot Quartz...");
? ? }
}
構(gòu)建調(diào)度配置類
@Configuration
public class QuartzConfig {
? ? @Bean
? ? public JobDetail jobDetail1(){
? ? ? ? return JobBuilder.newJob(MyFirstJob.class).storeDurably().build();
? ? }
? ? @Bean
? ? public Trigger trigger1(){
? ? ? ? SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
? ? ? ? ? ? ? ? //每一秒執(zhí)行一次
? ? ? ? ? ? ? ? .withIntervalInSeconds(1)
? ? ? ? ? ? ? ? //永久重復(fù),一直執(zhí)行下去
? ? ? ? ? ? ? ? .repeatForever();
? ? ? ? return TriggerBuilder.newTrigger()
? ? ? ? ? ? ? ? .withIdentity("trigger1","group1")
? ? ? ? ? ? ? ? .withSchedule(scheduleBuilder)
? ? ? ? ? ? ? ? .forJob(jobDetail1())
? ? ? ? ? ? ? ? .build();
? ? }
? ? // 每?jī)擅胗|發(fā)一次任務(wù)
? ? @Bean
? ? public Trigger trigger2(){
? ? ? ? return TriggerBuilder.newTrigger()
? ? ? ? ? ? ? ? .withIdentity("trigger2", "group1")
? ? ? ? ? ? ? ? .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))
? ? ? ? ? ? ? ? .forJob(jobDetail1())
? ? ? ? ? ? ? ? .build();
? ? }
}
啟動(dòng)StarterApplication 查看控制臺(tái)打印效果
全局異常與事物控制
Spring Boot事物支持
在使用Jdbc 作為數(shù)據(jù)庫訪問技術(shù)時(shí),Spring Boot框架定義了基于jdbc 的PlatformTransactionManager 接口的實(shí)現(xiàn)DataSourceTransactionManager,并在Spring Boot 應(yīng)用啟動(dòng)時(shí)自動(dòng)進(jìn)行配置。如果使用jpa 的話 Spring Boot 同樣提供了對(duì)應(yīng)實(shí)現(xiàn)。
這里Spring Boot 集成了Mybatis框架,Mybatis底層數(shù)據(jù)訪問層實(shí)現(xiàn)基于jdbc 來實(shí)現(xiàn),所以在Spring Boot 環(huán)境下對(duì)事物進(jìn)行控制,事物實(shí)現(xiàn)由Spring Boot實(shí)現(xiàn)并自動(dòng)配置,在使用時(shí)通過注解方式標(biāo)注相關(guān)方法加入事物控制即可
聲明式事物配置
@Transactional(propagation = Propagation.REQUIRED)
? ? public void saveUser(User user) {
? ? ? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用戶名不能為空!");
? ? ? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用戶密碼不能為空!");
? ? ? ? User temp = userMapper.queryUserByUserName(user.getUserName());
? ? ? ? AssertUtil.isTrue(null != temp, "該用戶已存在!");
? ? ? ? AssertUtil.isTrue(userMapper.save(user)<1,"用戶記錄添加失敗!");
? ? }
? ? @Transactional(propagation = Propagation.REQUIRED)
? ? public void updateUser(User user) {
? ? ? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用戶名不能為空!");
? ? ? ? AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用戶密碼不能為空!");
? ? ? ? User temp = userMapper.queryUserByUserName(user.getUserName());
? ? ? ? AssertUtil.isTrue(null != temp && !(temp.getId().equals(user.getId())), "該用戶已存在!");
? ? ? ? AssertUtil.isTrue(userMapper.update(user)<1,"用戶記錄添加失敗!");
? ? }
? ? @Transactional(propagation = Propagation.REQUIRED)
? ? public? void deleteUser(Integer id){
? ? ? ? AssertUtil.isTrue(null == id || null ==userMapper.queryById(id),"待刪除記錄不存在!");
? ? ? ? AssertUtil.isTrue(userMapper.delete(id)<1,"用戶刪除失敗!");
? ? }
Spring Boot 全局異常處理
SpringMvc 中對(duì)異常統(tǒng)一處理提供了相應(yīng)處理方式,推薦大家使用的是實(shí)現(xiàn)接口HandlerExceptionResolver的方式,對(duì)代碼侵入性較小。
在Spring Boot 應(yīng)用中同樣提供了對(duì)異常的全局性處理,相關(guān)注解如下:
@ControllerAdvice
該注解組合了@Component注解功能,最常用的就是作為全局異常處理的切面類,同時(shí)通過該注解可以指定包掃描的范圍。@ControllerAdvice約定了幾種可行的返回值,如果是直接返回model類的話,需要使用@ResponseBody進(jìn)行json轉(zhuǎn)換
@ExceptionHandler
? 該注解在Spring 3.X 版本引入,在處理異常時(shí)標(biāo)注在方法級(jí)別,代表當(dāng)前方法處理的異常類型有哪些 具體應(yīng)用以Restful 接口為例,測(cè)試保存用戶接口
全局異常應(yīng)用
異常拋出與全局捕捉
UserController 查詢接口
@ApiOperation(value = "根據(jù)用戶id查詢用戶記錄")
@ApiImplicitParam(name = "userId",value = "查詢參數(shù)",required = true,paramType = "path")
@GetMapping("user/{userId}")
public User queryUserByUserId(@PathVariable? Integer userId){
? ? return userService.queryUserByUserId(userId);
}
UserService 查詢業(yè)務(wù)方法,拋出ParamExceptions 異常
public User queryUserByUserId(Integer userId){
? ? AssertUtil.isTrue(true,"異常測(cè)試...");
? ? return userMapper.queryById(userId);
}
全局異常處理類GlobalExceptionHandler定義
@ControllerAdvice
public class GlobalExceptionHandler{
? ? /**
? ? * 全局異常處理 返回json
? ? * @param e
? ? * @return
? ? */
? ? @ExceptionHandler(value = Exception.class)
? ? @ResponseBody
? ? public ResultInfo exceptionHandler(Exception e){
? ? ? ? ResultInfo resultInfo=new ResultInfo();
? ? ? ? resultInfo.setCode(300);
? ? ? ? resultInfo.setMsg("操作失敗!");
? ? ? ? if(e instanceof ParamsException){
? ? ? ? ? ? ParamsException pe= (ParamsException) e;
? ? ? ? ? ? resultInfo.setMsg(pe.getMsg());
? ? ? ? ? ? resultInfo.setCode(pe.getCode());
? ? ? ? }
? ? ? ? return resultInfo;
? ? }
}
Postman 執(zhí)行測(cè)試效果
特定異常處理
通過@ExceptionHandler 標(biāo)注方法可以處理特定異常,這里以用戶未登錄異常為例,通過全局異常進(jìn)行統(tǒng)一處理
/**
* 用戶未登錄異常特殊處理 返回json
* @param authExceptions
* @return
*/
@ExceptionHandler(value = NoLoginException.class)
@ResponseBody
public? ResultInfo userNotLoginHandler(NoLoginException authExceptions){
? ? System.out.println("用戶未登錄異常處理。。。");
? ? return new ResultInfo(authExceptions.getCode(),authExceptions.getMsg());
}
在用戶添加接口中拋出未登錄異常為例進(jìn)行測(cè)試
@PutMapping("user")
@ApiOperation(value = "用戶添加")
@ApiImplicitParam(name = "user",value = "用戶實(shí)體類",dataType = "User")
public ResultInfo saveUser(@RequestBody? User user){
? ? if(1==1){
? ? ? ? throw? new NoLoginException();
? ? }
? ? ResultInfo resultInfo=new ResultInfo();
? ? ? ? userService.saveUser(user);
? ? return resultInfo;
}
SpringBoot 數(shù)據(jù)校驗(yàn)-Validation
日常項(xiàng)目開發(fā)中,對(duì)于前端提交的表單,后臺(tái)接口接收到表單數(shù)據(jù)后,為了程序的嚴(yán)謹(jǐn)性,通常后端會(huì)加入業(yè)務(wù)參數(shù)的合法校驗(yàn)操作來避免程序的非技術(shù)性bug,這里對(duì)于客戶端提交的數(shù)據(jù)校驗(yàn),SpringBoot通過spring-boot-starter-validation 模塊包含了數(shù)據(jù)校驗(yàn)的工作。
這里主要介紹Spring Boot中對(duì)請(qǐng)求數(shù)據(jù)進(jìn)行校驗(yàn),相關(guān)概念如下
JSR303/JSR-349: JSR303是一項(xiàng)標(biāo)準(zhǔn),只提供規(guī)范不提供實(shí)現(xiàn),規(guī)定一些校驗(yàn)規(guī)范即校驗(yàn)注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349是其升級(jí)版本,添加了一些新特性。
Hibernate Validation:Hibernate Validation是對(duì)這個(gè)規(guī)范的實(shí)現(xiàn),并增加了一些其他校驗(yàn)注解,如@Email,@Length,@Range等等
Spring Validation:Spring Validation對(duì)Hibernate Validation進(jìn)行了二次封裝,在Spring Mvc模塊中添加了自動(dòng)校驗(yàn),并將校驗(yàn)信息封裝進(jìn)了特定的類中
環(huán)境配置
實(shí)現(xiàn)參數(shù)校驗(yàn),程序必須引入spring-boot-starter-validation 依賴,只是在引入spring-boot-starter-web依賴時(shí),該模塊會(huì)自動(dòng)依賴spring-boot-starter-validation,所以程序中引入spring-boot-starter-web 會(huì)一并依賴spring-boot-starter-validation到項(xiàng)目中。
校驗(yàn)相關(guān)注解
注解功能
@AssertFalse可以為null,如果不為null的話必須為false
@AssertTrue可以為null,如果不為null的話必須為true
@DecimalMax設(shè)置不能超過最大值
@DecimalMin設(shè)置不能超過最小值
@Digits設(shè)置必須是數(shù)字且數(shù)字整數(shù)的位數(shù)和小數(shù)的位數(shù)必須在指定范圍內(nèi)
@Future日期必須在當(dāng)前日期的未來
@Past日期必須在當(dāng)前日期的過去
@Max最大不得超過此最大值
@Min最大不得小于此最小值
@NotNull不能為null,可以是空
@Pattern必須滿足指定的正則表達(dá)式
@Size集合、數(shù)組、map等的size()值必須在指定范圍內(nèi)
@Email必須是email格式
@Length長(zhǎng)度必須在指定范圍內(nèi)
@NotBlank字符串不能為null,字符串trin()后也不能等于“”
@NotEmpty不能為null,集合、數(shù)組、map等size()不能為0;字符串trin()后可以等于“”
@Range值必須在指定范圍內(nèi)
@URL必須是一個(gè)URL
參數(shù)校驗(yàn)注解使用
User實(shí)體類參數(shù)校驗(yàn)注解
public class User? implements Serializable {
? ? private Integer id;
? ? @NotBlank(message = "用戶名不能為空!")
? ? private String userName;
? ? @NotBlank(message = "用戶密碼不能為空!")
? ? @Length(min = 6, max = 10,message = "密碼長(zhǎng)度至少6位但不超過10位!")
? ? private String userPwd;
? ? private String email;
? ? /*
? ? ? 省略get set 方法?
? ? */
}
接口方法形參@Valid注解添加
@PostMapping("user02")
@ApiOperation(value = "用戶添加")
@ApiImplicitParam(name = "user02",value = "用戶實(shí)體類",dataType = "User")
public ResultInfo saveUser02(@Valid? User user){
? ? ResultInfo resultInfo=new ResultInfo();
? ? //userService.saveUser(user);
? ? return resultInfo;
}
全局異常錯(cuò)誤信息捕捉
/**
* 全局異常處理 返回json
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResultInfo exceptionHandler(Exception e){
? ? ResultInfo resultInfo=new ResultInfo();
? ? resultInfo.setCode(300);
? ? resultInfo.setMsg("操作失敗!");
? ? if(e instanceof ParamsException){
? ? ? ? ParamsException pe= (ParamsException) e;
? ? ? ? resultInfo.setMsg(pe.getMsg());
? ? ? ? resultInfo.setCode(pe.getCode());
? ? }else if(e instanceof BindException){
? ? ? ? BindException be = (BindException) e;
? ? ? ? resultInfo.setResult(be.getBindingResult().getFieldError().getDefaultMessage());
? ? }
? ? return resultInfo;
}
PostMan 接口測(cè)試
總結(jié)
今天課程主要介紹了SpringBoot中各種環(huán)境的整合與測(cè)試工作,持久層框架Mybatis集成與數(shù)據(jù)訪問基本操作,借助SpringBoot單元測(cè)試實(shí)現(xiàn)業(yè)務(wù)方法與控制器接口測(cè)試,同時(shí)集成了Swagger2 接口文件生成工具來快速生成接口文檔的功能。在web項(xiàng)目開發(fā)中常見的項(xiàng)目熱部署配置,這里集成DevTools工具來幫助web開發(fā)者在項(xiàng)目開發(fā)中不斷手動(dòng)啟動(dòng)服務(wù)器部署項(xiàng)目的繁瑣流程,通過引入EhCache 緩存技術(shù)來加快應(yīng)用程序數(shù)據(jù)的訪問效率,然后對(duì)于項(xiàng)目中常見的定時(shí)任務(wù)的執(zhí)行,這里集成了Quartz 任務(wù)調(diào)度框架來實(shí)現(xiàn)任務(wù)定時(shí)執(zhí)行處理,最后提到了SpringBoot 應(yīng)用中對(duì)象項(xiàng)目事物控制與異常統(tǒng)一處理,從而提高項(xiàng)目代碼的健壯性與數(shù)據(jù)的一致性,借助SpringBoot 的Validation 實(shí)現(xiàn)后端數(shù)據(jù)參數(shù)的校驗(yàn)機(jī)制,結(jié)合全局異常來對(duì)校驗(yàn)結(jié)果進(jìn)行輸出操作,提高后端應(yīng)參數(shù)處理的嚴(yán)謹(jǐn)性。