SpringBoot_第二天

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;

? ? @Email

? ? 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)性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容