map = new LinkedHashMap<>();
//anon表示可以匿名访问
map.put("/index.html","anon");
map.put("/user/login","anon");
//其他需要权限验证
map.put("/**","authc");
//设置到bean上
bean.setFilterChainDefinitionMap(map);
return bean;
}
}
```
接口登录验证
```java
//验证账号密码
//1、获取到单签“用户”
Subject currentUser = SecurityUtils.getSubject();
//2、判断是否没有认证
if(!currentUser.isAuthenticated()){
//创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("123", "432");
//去进行登录认证
currentUser.login(token);
}
```
```java
package com.xxgc.springboot.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* @program: springboot
* @description:
* @author: Mr.abel(ShiJiaYi)
* 授权Realm
**/
public class UserRealm extends AuthorizingRealm {
//去获取到授权的信息
//获取当前用户角色用户、权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//从数据库获取当前用户的账号、密码信息(认证)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//getName 当前realm的名字
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo("123", "321", getName());
return info;
}
}
```

### 4.3 整合SpringSecurity
#### 4.3.1 简介
学习权限框架必须搞懂的两个词语 **认证 授权**
1. 认证:用户名,密码错误,认证失败
2. 授权:当前用户有哪些权限
### 4.4 整合Swagger2
#### 4.4.1 Swagger导包
```xml
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
com.github.xiaoymin
knife4j-spring-boot-starter
2.0.4
```
#### 4.4.2 配置swagger
```java
/**
* @program: web
* @description: swagger配置
* @author: Mr.abel(ShiJiaYi)
**/
@Configuration
@EnableSwagger2
public class Swagger2Config{
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// .enable(swaggerEnable)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.xlkg.web.controller")) //指定扫描包
// .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.paths(PathSelectors.any())
//只显示api路径下的页面
//.paths(Predicates.and(PathSelectors.regex("/api/.*")))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("标题")
.description("描述")
.version("1.0.0")
.build();
}
}
```
#### 4.4.3 Swagger拦截器无法访问
```java
/**
* @program: web
* @description: 拦截器规则
* @author: Mr.abel(ShiJiaYi)
**/
@Configuration
public class MyMvcConfig extends WebMvcConfigurationSupport {
/**
* 解决swagger-ui.html 404无法访问的问题
*/
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 解决静态资源无法访问
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
// 解决swagger无法访问
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
// 解决swagger的js文件无法访问
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
```
#### 4.4.4 常用注解
```java
@Api(tags = "类上的描述")
```
```java
@ApiOperation(value = "方法的描述", notes = "接口的描述")
```
```java
@ApiModel(value = "模型的描述", description = "")
```
```java
@ApiModelProperty("字段的描述")
```
### 4.5 整合Mybatis-plus
#### 4.5.1 简介
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

- **无侵入**:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- **损耗小**:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- **强大的 CRUD 操作**:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- **支持 Lambda 形式调用**:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- **支持主键自动生成**:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- **支持 ActiveRecord 模式**:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- **支持自定义全局通用操作**:支持全局通用方法注入( Write once, use anywhere )
- **内置代码生成器**:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- **内置分页插件**:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- **分页插件支持多种数据库**:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- **内置性能分析插件**:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- **内置全局拦截插件**:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
#### 4.5.2 安装
```xml
com.baomidou
mybatis-plus-boot-starter
最新版本
```
#### 4.5.3 添加配置
```yaml
# DataSource Config
spring:
datasource:
driver-class-name: org.h2.Driver
schema: classpath:db/schema-h2.sql
data: classpath:db/data-h2.sql
url: jdbc:h2:mem:test
username: root
password: test
```
#### 4.5.4 开启Mapper扫描
```java
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
#### 4.5.5 代码生成/逆向工程
添加依赖
```xml
mysql
mysql-connector-java
runtime
com.baomidou
mybatis-plus-boot-starter
3.5.1
com.baomidou
mybatis-plus-generator
3.5.1
org.apache.velocity
velocity
1.7
```
添加生成模板
```java
package com.xxgc.springboot.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import java.util.Collections;
public class CodeGenerator {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/spring_boot_study?serverTimezone=GMT%2b8";
String username = "root";
String password = "123456";
FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> {
builder.author("Mr.Abel") // 设置作者
.fileOverride() // 覆盖已生成文件
.disableOpenDir() //禁止打开输出目录
.outputDir("D:\\WorkSpace\\java-study\\SpringBoot备课\\SpringMVC\\src\\main\\java\\"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.xxgc.springboot") // 设置父包名
.moduleName(null) // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D:\\WorkSpace\\java-study\\SpringBoot备课\\SpringMVC\\src\\main\\resources\\mapper\\")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.entityBuilder().enableLombok(); //启动lombok
builder.mapperBuilder().enableMapperAnnotation().build(); //启用@mapper注释
builder.controllerBuilder().enableHyphenStyle().enableRestStyle(); //启用驼峰转连字符样式
builder.addInclude("user_info"); // 设置需要生成的表名
})
.execute();
}
}
```
添加接口模板(请使用新版)
新版
对于插入时间的控制,可以在mysql数据库用 CURRENT_TIMESTAMP 或 NOW()都可以

```java
package ${package.Controller};
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
/**
*
* $!{table.comment} 前端控制器
*
*
* @author ${author}
* @since ${date}
*/
@RestController
@Api(tags = "$!{table.comment}相关接口")
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
public class ${table.controllerName} {
@Resource
private ${table.serviceName} ${table.entityPath}Service;
/**
* 查询所有接口
* @return
*/
@ApiOperation(value = "$!{table.comment}查询所有接口", notes = "查询所有接口")
@GetMapping
public Result> findAll() {
return Result.ok(${table.entityPath}Service.list());
}
/**
* 根据id查询数据接口
* @param id
* @return
*/
@ApiOperation(value = "$!{table.comment}查更具id查询", notes = "根据id查询数据接口")
@GetMapping("/{id}")
public Result<${entity}> findOne(@PathVariable Integer id) {
return Result.ok(${table.entityPath}Service.getById(id));
}
/**
* 分页查询接口
* @param pageNum
* @param pageSize
* @return
*/
@ApiOperation(value = "$!{table.comment}分页查询接口", notes = "分页查询接口")
@GetMapping("/page")
public Result> findPage(@RequestParam Integer pageNum,@RequestParam Integer pageSize) {
QueryWrapper<${entity}> queryWrapper = new QueryWrapper<>();
return Result.ok(${table.entityPath}Service.page(new Page<>(pageNum, pageSize), queryWrapper));
}
/**
* 新增和更新接口
* @param
* @return
*/
@ApiOperation(value = "$!{table.comment}新增和更新接口", notes = "新增和更新接口")
@PostMapping
public Result save(@RequestBody ${entity} ${table.entityPath}) {
${table.entityPath}Service.saveOrUpdate(${table.entityPath});
return Result.ok();
}
/**
* 删除接口
* @param id
* @return
*/
@ApiOperation(value = "$!{table.comment}删除接口", notes = "删除接口")
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
${table.entityPath}Service.removeById(id);
return Result.ok();
}
/**
* 批量删除接口
* @param ids
* @return
*/
@ApiOperation(value = "$!{table.comment}批量删除接口", notes = "批量删除接口")
@PostMapping("/del/batch")
public Result deleteBatch(@RequestBody List ids) {
${table.entityPath}Service.removeByIds(ids);
return Result.ok();
}
}
```
旧版
```java
package ${package.Controller};
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
/**
*
* $!{table.comment} 前端控制器
*
*
* @author ${author}
* @since ${date}
*/
@RestController
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
public class ${table.controllerName} {
@Resource
private ${table.serviceName} ${table.entityPath}Service;
/**
* 查询所有接口
* @return
*/
@GetMapping
public JsonData findAll() {
return JsonData.buildSuccess(${table.entityPath}Service.list());
}
/**
* 根据id查询数据接口
* @param id
* @return
*/
@GetMapping("/{id}")
public JsonData findOne(@PathVariable Integer id) {
return JsonData.buildSuccess(${table.entityPath}Service.getById(id));
}
/**
* 分页查询接口
* @param pageNum
* @param pageSize
* @return
*/
@GetMapping("/page")
public JsonData findPage(@RequestParam Integer pageNum,@RequestParam Integer pageSize) {
QueryWrapper<${entity}> queryWrapper = new QueryWrapper<>();
return JsonData.buildSuccess(${table.entityPath}Service.page(new Page<>(pageNum, pageSize), queryWrapper));
}
/**
* 新增和更新接口
* @param user
* @return
*/
@PostMapping
public JsonData save(@RequestBody ${entity} ${table.entityPath}) {
${table.entityPath}Service.saveOrUpdate(${table.entityPath});
return JsonData.buildSuccess();
}
/**
* 删除接口
* @param id
* @return
*/
@DeleteMapping("/{id}")
public JsonData delete(@PathVariable Integer id) {
${table.entityPath}Service.removeById(id);
return JsonData.buildSuccess();
}
/**
* 批量删除接口
* @param ids
* @return
*/
@PostMapping("/del/batch")
public JsonData deleteBatch(@RequestBody List ids) {
${table.entityPath}Service.removeByIds(ids);
return JsonData.buildSuccess();
}
}
```

添加统一返回工具类(使用新版)
**新版** 2022年9月更新
```java
package com.xxgc.springboot.util;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @program: springboot
* @description: 统一返回格式
* @author: Mr.abel(ShiJiaYi)
**/
@Data
@ApiModel
public class Result implements Serializable {
private static final String SUCCESSMSG = "操作成功!";
@ApiModelProperty(value = "请求状态码")
private int code;
@ApiModelProperty(value = "响应信息")
private String msg;
//属性为 空("") 或者为 NULL 都不序列化
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModelProperty(value = "数据")
private T data;
public Result(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static Result ok(){
return new Result<>(200,SUCCESSMSG,null);
}
public static Result ok(T data){
return new Result<>(200,SUCCESSMSG,data);
}
public static Result error(String error) {
return new Result<>(500, error, null);
}
/**--------------当公司还有其他业务状态返回可以在以下添加---------------*/
}
```
旧版
```java
package com.xxgc.springboot.util;
public class JsonData {
/**
* 状态码
*/
private Integer code;
/**
* 数据
*/
private Object data;
/**
* 状态信息
*/
private String msg;
public JsonData(){}
public JsonData(Integer code,Object data,String msg){
this.code = code;
this.data = data;
this.msg = msg;
}
/**
* 成功
*/
public static JsonData buildSuccess(){
return new JsonData(0,null,null);
}
/**
* 成功
* @param data 数据
*/
public static JsonData buildSuccess(Object data){
return new JsonData(0,data,null);
}
/**
* 成功
* @param data 数据
* @param msg 状态信息
*/
public static JsonData buildSuccess(Object data,String msg){
return new JsonData(0,data,msg);
}
/**
* 失败
* @param msg 数据
*/
public static JsonData buildError(String msg){
return new JsonData(-1,null,msg);
}
/**
* 失败
* @param code 状态码
* @param msg 数据
*/
public static JsonData buildError(Integer code,String msg){
return new JsonData(code,null,msg);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
```
#### 4.5.6 使用ApiPost(PostMan)
使用ApiPost或PostMan完成对用户的注册
#### 4.5.7 生成代码时对Swagger的支持
```java
.globalConfig(builder -> {
builder.author("Mr.abel(ShiJiaYi)") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.commentDate("yyyy-MM-dd")
.outputDir("C://Users//十一//Desktop//喜乐快够官网//src//main//java"); // 指定输出目录
})
```
#### 4.5.8 Mybatis-plus基本使用
##### 4.5.8.1逻辑删除
```yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: valid # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
```
逻辑删除在于保留数据,以方便后续可能会出现的相关性错误。
flag 已删除 true 1 false 0 delete-value应该为1
valid 有效列 true 1 false 0 delete-value应该为0
##### 4.5.8.2 通用枚举类型
在数据库创建年级字段grade
创建枚举类
```java
package com.xxgc.springboot.entity.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
/**
* @program: springboot
* @description: 枚举
* @author: Mr.abel(ShiJiaYi)
**/
@Getter
public enum GradeEnum {
PRIMARY(1, "小学"),
SECONDORY(2, "中学"),
HIGH(3, "高中");
GradeEnum(int grade, String descp) {
this.grade = grade;
this.descp = descp;
}
@EnumValue//标记数据库存的值是code
private final int grade;
private final String descp;
}
```
```java
@ApiModelProperty("年级")
private GradeEnum grade;
```
在实体类使用枚举
```yaml
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.xxgc.springboot.entity.enums
```
配置枚举扫描
##### 4.5.8.3 自动填充处理器
添加更新字段 update_time
```java
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
}
}
```
实体类添加
```java
@TableField(fill = FieldFill.INSERT_UPDATE)
@ApiModelProperty("最后一次更新")
private LocalDateTime updateTime;
```
##### 4.5.8.4 数据安全保护
```yml
// 加密配置 mpw: 开头紧接加密内容( 非数据库配置专用 YML 中其它配置也是可以使用的 )
spring:
datasource:
url: mpw:qRhvCwF4GOqjessEB3G+a5okP+uXXr96wcucn2Pev6Bf1oEMZ1gVpPPhdDmjQqoM
password: mpw:Hzy5iliJbwDHhjLs1L0j6w==
username: mpw:Xb+EgsyuYRXw7U7sBJjBpA==
```
```java
// 生成 16 位随机 AES 密钥
String randomKey = AES.generateRandomKey();
// 随机密钥加密
String result = AES.encrypt(data, randomKey);
```
使用
```java
// Jar 启动参数( idea 设置 Program arguments , 服务器可以设置为启动环境变量 )
--mpw.key=d1104d7c3b616f0b
```
##### 4.5.8.5 自定义主键策略
```sql
CREATE TABLE `orders` (
`id` varchar(200) NOT NULL COMMENT 'id',
`name` varchar(200) NOT NULL COMMENT '订单名',
`quantity` int(3) NOT NULL DEFAULT '1' COMMENT '订单数量',
`flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '有效字段',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单'
```
为什么使用主键策略?
答:如订单数据,在数据传输中如果使用自增长会泄露真实订单数量。如果是用户表,那会泄露真实的用户数量
解决方案:
| 值 | 描述 |
| :---------: | :----------------------------------------------------------: |
| AUTO | 数据库 ID自增,这种情况下将表中主键设置为自增,否则,没有设置主动设置id值进行插入时会报错 |
| NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里默认 ASSIGN_ID),注意这里官网文档有误 |
| INPUT | insert 前自行 set 主键值,在采用IKeyGenerator类型的ID生成器时必须为INPUT |
| ASSIGN_ID | 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法) |
| ASSIGN_UUID | 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法) |
###### 4.5.8.5.1 AUTO`自动增长策略
这个配合数据库使用,`Mysql`可以,但是`Oracle`不行。不配合会报错,这里就不细展开了,有兴趣的同学可以去试一试。
在主键字段上加上 `@TableId(type = IdType.AUTO)`
```java
//指定主键使用数据库ID自增策略
@TableId(type = IdType.AUTO)
private Integer id;
```
###### 4.5.8.5.2 Input(自定义输入策略)
自定义输入策略:如果不想使用数据库的自增主键,也可以使用INPUT进行自己传递主键即可,进行插入工作,但在插入之前一定要检查数据库是否已经存在了该主键。
Mybatis-Plus 内置了如下数据库主键序列(如果内置支持不满足你的需求,可实现 IKeyGenerator 接口来进行扩展):
```tex
DB2KeyGenerator
H2KeyGenerator
KingbaseKeyGenerator
OracleKeyGenerator
PostgreKeyGenerator
```
**使用方法如下所示:**
首先添加@Bean
```java
@Bean
public OracleKeyGenerator oracleKeyGenerator(){
return new OracleKeyGenerator();
}
```
然后实体类配置主键 `Sequence`,指定主键策略为`IdType.INPUT`即可
```java
@Data
@KeySequence(value = "SEQ_ACL_ROLE" , clazz = Integer.class)
public class AclUser implements Serializable {
private static final long serialVersionUID = 780903014942735924L;
@TableId(value = "ID",type = IdType.INPUT)
private Integer id;
```
###### 4.5.8.5.3`ASSIGN_ID`(雪花算法)

如果不设置type值,默认则使用IdType.ASSIGN_ID策略(自 3.3.0 起)。该策略会使用雪花算法自动生成主键 ID,主键类型为 Long 或 String。
雪花算法(SnowFlake)是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且 ID 引入了时间戳,基本上保持自增的。
使用方法
指定主键生成策略使用雪花算法(默认策略)
```java
@ApiModelProperty("id")
//指定主键生成策略使用雪花算法(默认策略)
@TableId(type = IdType.ASSIGN_ID)
private String id;
```
###### 4.5.8.5.4 ASSIGN_UUID
ASSIGN_UUID(不含中划线的`UUID`) 如果使用 `IdType.ASSIGN_UUID` 策略,则会自动生成不含中划线的 `UUID` 作为主键,主键类型为 `String`。
**使用方法**
```java
@Data
public class UserInfo {
//指定主键生成策略为不含中划线的UUID
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String userName;
private String passWord;
}
```
###### 4.5.8.5.5 NONE(无状态)
如果使用 `IdType.NONE` 策略,表示未设置主键类型(注解里等于跟随全局,全局里约等于` INPUT`)
假设我们希望默认全部都使用 `AUTO `策略(数据库`ID`自增),那么可以在 `application.properties `中添加如下配置进行修改:
```properties
mybatis-plus.global-config.db-config.id-type=auto
```
##### 4.5.8.6 多表联合查询
### 4.6 整合Redis
### 4.7 整合MongoDB
### 4.8 整合IOC和AOP
#### 4.8.1 IOC
控制反转(IOC)和依赖注入(DI)是同一个概念,目的在于降低系统耦合,将类的实例化工作交给Spring代理,主要用到的设计模式为工厂模式,通过Java反射机制实现类的自动注入。
IoC(Inverse of Control:控制反转)是⼀种设计思想,就是 将原本在程序中⼿动创建对象的控制 权,交由Spring框架来管理。 IoC 在其他语⾔中也有应⽤,并⾮ Spring 特有。 IoC 容器是 Spring ⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各 种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤ 程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚⼀ 样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如 何被创建出来的。
#### 4.8.2 AOP
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
能够将那些与业务⽆关,却为业务模块所共 同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复 代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
##### 4.8.2.1概念

1. 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的Advisor或拦截器实现。将那些影响了 多个类的公共行为封装到一个可重用模块。简单地说,就是将那些与业务无关,却为业务模块所共同调用的 逻辑或责任封装起来,比如日志记录,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
2. 切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。
3. 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
4. 通知(Advice):在特定的连接点,AOP框架执行的动作。
5. 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。
6. AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
7. 引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。
8. 编织(Weaving):组装方面来创建一个被通知对象。
##### 4.8.2.2 使用场景
- 权限检查
- 缓存
- 内容传递
- 错误处理
- 日志记录,跟踪,优化,校准
- 性能优化,效率检查
##### 4.8.2.3 使用
Spring Boot使用AOP需要添加spring-boot-starter-aop依赖
```xml
org.springframework.boot
spring-boot-starter-aop
```
定义通知
通知有五种类型,分别是:
1. 前置通知(@Before):在目标方法调用之前调用通知
2. 后置通知(@After):在目标方法完成之后调用通知
3. 环绕通知(@Around):在被通知的方法调用之前和调用之后执行自定义的方法
4. 返回通知(@AfterReturning):在目标方法成功执行之后调用通知
5. 异常通知(@AfterThrowing):在目标方法抛出异常之后调用通知
代码中定义了三种类型的通知,使用@Before注解标识前置通知,打印“beforeAdvice...”,使用@After注解标识后置通知,打印“AfterAdvice...”,使用@Around注解标识环绕通知,在方法执行前和执行之后分别打印“before”和“after”。这样一个切面就定义好了,代码如下:
```java
@Aspect
@Component
public class AopAdvice {
@Pointcut("execution (* com.xxgc.springboot.controller.*.*(..))")
public void test() {
}
@Before("test()")
public void beforeAdvice() {
System.out.println("beforeAdvice...");
}
@After()
public void afterAdvice() {
System.out.println("afterAdvice...");
}
@Around()
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("before");
try {
proceedingJoinPoint.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("after");
}
}
```
### 4.9 整合Quartz定时框架
#### 补充讲解 Timer
```java
public class TimerTest {
private static int i = 3;
//Timer Java自带的定时任务类
public static void main(String[] args) {
//任务1
TimerTask timerTask = new TimerTask(){
public void run(){
int j = 1 / i;
System.out.println("我被执行了");
i--;
}
};
//任务2
TimerTask timerTask2 = new TimerTask(){
public void run(){
System.out.println("22222222222222222");
}
};
//计时器
Timer timer = new Timer();
//开始执行任务 1秒后执行 每3秒执行一次
timer.schedule(timerTask,1000, 3000);
//开始执行任务 1秒后执行 每3秒执行一次
timer.schedule(timerTask2,1000, 4000);
}
}
```
timer有一个问题,在创建schedule,如果某一个日程报错,其他日志将不会继续执行。
#### 补充讲解:ScheduledExecutorService
官方发现后提供了ScheduledExecutorService类来解决Timer所有的问题。
#### 补充讲解 Spring Task
Spring Framework自带的定时任务,核心使用了Timer和ScheduledExecutorService两种方式混合实现
SpringBoot项目使用Spring Task分为两步
1、开启定时任务
```java
//开启定时任务
@EnableScheduling
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
```
2、添加定时任务
```java
@Component
public class SpringTaskTest {
//cron 表达式
//星期1-星期5 每天 19点35分0秒执行
@Scheduled(cron = "0 35 19 * * 1-5")
public void doZhxStudy(){
System.out.println("周焕熙起来学习");
}
}
```
#### 4.9.1 Quartz介绍导入
Quartz 石英 》手表 》 时间 》 任务调度框架 Timer
```xml
org.springframework.boot
spring-boot-starter-quartz
```
- Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目
- Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
- Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
- Quartz 允许程序开发人员根据时间的间隔来调度作业。
- Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
#### 4.9.2 名词解析
1. **Job** 表示一个工作,要执行的具体内容。此接口中只有一个方法
```java
void execute(JobExecutionContext context) throws JobExecutionException;
```
1. **JobDetail** (工作细节)表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务**调度的方案和策略**。
2. **Trigger** 触发器,代表一个调度参数的配置,**什么时候去调**。
3. **Scheduler** (日程)调度器,代表一个**调度容器**,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
**Scheduler** 调度器 》 **Trigger** 触发器
》 **JobDetail** 工作策略 》**Job** 工作
#### 4.9.3 常用API
1. Scheduler 用于与调度程序交互的主程序接口
调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过Schedule.scheduleJob方法安排进任务计划),任务触发trigger,任务才会执行
2. Job 任务类,如果自定义任务的话,需要实现Job接口,实现**void execute(JobExecutionContext context) throws** **JobExecutionException;**方法
3. JobDetail 定义任务Job的实例,通过JobBuilder类创建
4. JobDataMap Map接口实现类,存储Job实例执行过程中的数据
5. Trigger触发器,用来执行Job任务
6. JobBuilder 用于声明一个任务实例,可定义该任务的任务名,组名等
7. TriggerBuilder 触发器创建类,用于创建触发器trigger的实例
8. JobListener、TriggerListener、SchedulerListener监听器,用于对组件的监听
#### 4.9.4 初体验
```java
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("数据库备份时间:"+string);
}
}
```
调用
```java
public class TestQuartz {
public static void main(String[] args) throws SchedulerException {
// 1. 创建调度器Scheduler,从工厂中获取默认调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建JobDetail实例
// HelloJob.class 实现Job接口的实例类
// "JobDetailName" JobDetail的名称,用来标识任务
// "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT
// build() 创建JobDetail实例
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("JobDetailName", "JobDetailGroup")
.build();
// 3. 触发器 Trigger
// "TriggerName" 触发器名称 用来标识触发器
// "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT
// startNow() 开始时间 立刻开始
// withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次
// 创建触发器 Trigger实例
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("TriggerName", "TriggerGroup")
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
// 将任务与触发器相关联
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
}
}
```
#### 4.9.5 Job和JobDetail
##### 4.9.5.1 Job
- Job:工作任务调度的接口,任务类需要实现该接口,该接口中的execute方法,类似JDK提供的TimeTask中的run方法,在里面编写任务执行的业务逻辑代码
##### 4.9.5.2 Job生命周期
- Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法钱会创建一个新的Job实例,当调用结束后,关联的Job对象就会被释放,被垃圾回收机制回收
- 修改HelloJob类,添加无参构造方法
- ```java
public class HelloJob implements Job {
public HelloJob() {
System.out.println("HelloJob任务被创建");
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("数据库备份时间:"+string);
}
}
```
- 每执行一次任务,HelloJo类就会被创建一次
##### 4.9.5.3 JobDetail
1. jobDetail为Job实例提供了许多设置属性,以及JobDetalMap成员变量属性,用来存储特定Job实例的状态信息,调度器需要借助JobDatail对象添加Job实例
2. 重要属性:name、group、jobClass、jobDataMap
3. 修改TestQuartz测试类
```java
public class TestQuartz {
public static void main(String[] args) throws SchedulerException {
// 1. 创建调度器Scheduler,从工厂中获取默认调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建JobDetail实例
// HelloJob.class 实现Job接口的实例类
// "JobDetailName" JobDetail的名称,用来标识任务
// "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT
// build() 创建JobDetail实例
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("JobDetailName", "JobDetailGroup")
.build();
// 获取jobDetail设置的名称
String jobDetailName = jobDetail.getKey().getName();
System.out.println("设置的任务名称:"+jobDetailName);
// 获取设置的任务组名称
String jobDetailGroup = jobDetail.getKey().getGroup();
System.out.println("设置的任务组名称:"+jobDetailGroup);
// 获取Job实现类的名称
String simpleName = jobDetail.getJobClass().getSimpleName();
System.out.println("任务实例类名称:"+simpleName);
// 3. 触发器 Trigger
// "TriggerName" 触发器名称 用来标识触发器
// "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT
// startNow() 开始时间 立刻开始
// withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次
// 创建触发器 Trigger实例
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("TriggerName", "TriggerGroup")
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
// 将任务与触发器相关联
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
}
}
```
- 打印JobDetail信息
```tex
设置的任务名称:JobDetailName
设置的任务组名称:JobDetailGroup
任务实例类名称:HelloJob
HelloJob任务被创建
数据库备份时间:2021-08-27 18:28:24
HelloJob任务被创建
数据库备份时间:2021-08-27 18:28:29
······
```
#### 4.9.6 JobExecutionContext
- 当Scheduler调用一个ob,就会将JobExecutionContext传递给Job的execute()方法;
- Job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及Job本身的明细数据。
- 修改HelloJob类:
- ```java
public class HelloJob implements Job {
public HelloJob() {
System.out.println("HelloJob任务被创建");
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
String jobDetailName = jobDetail.getKey().getName();
System.out.println("任务名称:"+jobDetailName);
String jobDetailGroup = jobDetail.getKey().getGroup();
System.out.println("任务组名称:"+jobDetailGroup);
String jobSimpleName = jobDetail.getJobClass().getSimpleName();
System.out.println("任务实现类名称:"+jobSimpleName);
Trigger trigger = context.getTrigger();
String triggerName = trigger.getKey().getName();
System.out.println("触发器名称:"+triggerName);
String triggerGroup = trigger.getKey().getGroup();
System.out.println("触发器组名称:"+triggerGroup);
// 获取其他信息
Date date = context.getFireTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
System.out.println("任务执行的时间"+format);
Date nextFireTime = context.getNextFireTime();
String format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(nextFireTime);
System.out.println("下一次任务执行的时间"+format1);
String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("数据库备份时间:"+string);
}
}
```
- ```tex
HelloJob任务被创建
任务名称:JobDetailName
任务组名称:JobDetailGroup
任务实现类名称:HelloJob
触发器名称:TriggerName
触发器组名称:TriggerGroup
任务执行的时间2021-08-30 11:00:02
下一次任务执行的时间2021-08-30 11:02:30
数据库备份时间:2021-08-30 11:00:02
```
#### 4.9.7 JobDataMap
- 在进行任务调度时,JobDataMap存储在JobExecutionContext中
- JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它
- JobDataMap实现了JDK的Map接口

##### 4.9.7.1 修改TestQuartz类
```java
public class TestQuartz {
public static void main(String[] args) throws SchedulerException {
// 1. 创建调度器Scheduler,从工厂中获取默认调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建JobDetail实例
// HelloJob.class 实现Job接口的实例类
// "JobDetailName" JobDetail的名称,用来标识任务
// "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT
// build() 创建JobDetail实例
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("message","打印日志");
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("JobDetailName", "JobDetailGroup")
.usingJobData(jobDataMap)
.build();
/*// 获取jobDetail设置的名称
String jobDetailName = jobDetail.getKey().getName();
System.out.println("设置的任务名称:"+jobDetailName);
// 获取设置的任务组名称
String jobDetailGroup = jobDetail.getKey().getGroup();
System.out.println("设置的任务组名称:"+jobDetailGroup);
// 获取Job实现类的名称
String simpleName = jobDetail.getJobClass().getSimpleName();
System.out.println("任务实例类名称:"+simpleName);*/
// 3. 触发器 Trigger
// "TriggerName" 触发器名称 用来标识触发器
// "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT
// startNow() 开始时间 立刻开始
// withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次
// 创建触发器 Trigger实例
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("TriggerName", "TriggerGroup")
.usingJobData(jobDataMap)
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
// 将任务与触发器相关联
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
}
}
```
##### 4.9.7.2 修改HelloJob类
```java
public class HelloJob implements Job {
public HelloJob() {
System.out.println("HelloJob任务被创建");
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
/*JobDetail jobDetail = context.getJobDetail();
String jobDetailName = jobDetail.getKey().getName();
System.out.println("任务名称:"+jobDetailName);
String jobDetailGroup = jobDetail.getKey().getGroup();
System.out.println("任务组名称:"+jobDetailGroup);
String jobSimpleName = jobDetail.getJobClass().getSimpleName();
System.out.println("任务实现类名称:"+jobSimpleName);
Trigger trigger = context.getTrigger();
String triggerName = trigger.getKey().getName();
System.out.println("触发器名称:"+triggerName);
String triggerGroup = trigger.getKey().getGroup();
System.out.println("触发器组名称:"+triggerGroup);*/
// 获取jobDataMap
JobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap();
String JobDetailMessage = jobDetailDataMap.getString("message");
System.out.println("任务信息:"+JobDetailMessage);
JobDataMap triggerJobDataMap = context.getTrigger().getJobDataMap();
String triggerMessage = triggerJobDataMap.getString("message");
System.out.println("触发器信息:"+triggerMessage);
String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("数据库备份时间:"+string);
}
}
```
结果
```tex
HelloJob任务被创建
任务信息:打印日志
触发器信息:打印日志
数据库备份时间:2021-08-30 10:44:24
······
```
- Job实现类中添加setter方法对应JobDataMap的键值,Quartz[框架](https://so.csdn.net/so/search?q=框架&spm=1001.2101.3001.7020)默认的JobFactory实现类在初始化job实例对象时会自动地调用这些setter方法。
```java
public class TestQuartz {
public static void main(String[] args) throws SchedulerException {
// 1. 创建调度器Scheduler,从工厂中获取默认调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建JobDetail实例
// HelloJob.class 实现Job接口的实例类
// "JobDetailName" JobDetail的名称,用来标识任务
// "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT
// build() 创建JobDetail实例
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("JobDetailName", "JobDetailGroup")
.usingJobData("message","任务信息")
.build();
/*// 获取jobDetail设置的名称
String jobDetailName = jobDetail.getKey().getName();
System.out.println("设置的任务名称:"+jobDetailName);
// 获取设置的任务组名称
String jobDetailGroup = jobDetail.getKey().getGroup();
System.out.println("设置的任务组名称:"+jobDetailGroup);
// 获取Job实现类的名称
String simpleName = jobDetail.getJobClass().getSimpleName();
System.out.println("任务实例类名称:"+simpleName);*/
// 3. 触发器 Trigger
// "TriggerName" 触发器名称 用来标识触发器
// "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT
// startNow() 开始时间 立刻开始
// withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次
// 创建触发器 Trigger实例
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("TriggerName", "TriggerGroup")
.usingJobData("message","触发器信息")
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
// 将任务与触发器相关联
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
}
}
```
```java
public class HelloJob implements Job {
private String message;
public HelloJob() {
System.out.println("HelloJob任务被创建");
}
public void setMessage(String message) {
this.message = message;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
/*JobDetail jobDetail = context.getJobDetail();
String jobDetailName = jobDetail.getKey().getName();
System.out.println("任务名称:"+jobDetailName);
String jobDetailGroup = jobDetail.getKey().getGroup();
System.out.println("任务组名称:"+jobDetailGroup);
String jobSimpleName = jobDetail.getJobClass().getSimpleName();
System.out.println("任务实现类名称:"+jobSimpleName);
Trigger trigger = context.getTrigger();
String triggerName = trigger.getKey().getName();
System.out.println("触发器名称:"+triggerName);
String triggerGroup = trigger.getKey().getGroup();
System.out.println("触发器组名称:"+triggerGroup);*/
/*// 获取jobDataMap
JobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap();
String JobDetailMessage = jobDetailDataMap.getString("message");
System.out.println("任务信息:"+JobDetailMessage);
JobDataMap triggerJobDataMap = context.getTrigger().getJobDataMap();
String triggerMessage = triggerJobDataMap.getString("message");
System.out.println("触发器信息:"+triggerMessage);*/
/*// 获取其他信息
Date date = context.getFireTime();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
System.out.println("任务执行的时间"+format);
Date nextFireTime = context.getNextFireTime();
String format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(nextFireTime);
System.out.println("下一次任务执行的时间"+format1);*/
System.out.println(message);
String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("数据库备份时间:"+string);
}
}
```
- 成员的类型和名称需要和在TestQuartz中设置的对应
- jobDetail和trigger的jobDataMap的key值相同,trigger的jobDataMap会覆盖jobDetail的
#### 4.9.8 有状态的Job和无状态的Job
- @PersistJobDataAfterExecution注解的使用
- 有状态的Job可以理解为多次job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中
- 默认的无状态job每次调用时都会创建一个新的JobDataMap。
- ```java
public class TestQuartz01 {
public static void main(String[] args) throws SchedulerException {
// 1. 获取调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 获取JobDetail
JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
.withIdentity("JobDetail", "JobDetailGroup")
.usingJobData("count",0)
.build();
// 3. 获取trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("Trigger", "TriggerGroup")
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
// 4. 通过调度器绑定任务和触发器
scheduler.scheduleJob(jobDetail,trigger);
//5. 启动
scheduler.start();
}
}
```
- ```java
public class TestJob implements Job {
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("TestJob");
count++;
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
jobDataMap.put("count",count);
System.out.println("TestJob中的count值:"+count);
System.out.println("jobDataMap的count值:"+jobDataMap.getInt("count"));
}
}
```
- ```tex
TestJob
TestJob中的count值:1
jobDataMap的count值:1
TestJob
TestJob中的count值:1
jobDataMap的count值:1
······
```
- 添加@PersistJobDataAfterExecution注解
- ```java
@PersistJobDataAfterExecution
public class TestJob implements Job {
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("TestJob");
count++;
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
jobDataMap.put("count",count);
System.out.println("TestJob中的count值:"+count);
System.out.println("jobDataMap的count值:"+jobDataMap.getInt("count"));
}
}
```
- ```java
TestJob
TestJob中的count值:1
jobDataMap的count值:1
TestJob
TestJob中的count值:2
jobDataMap的count值:2
TestJob
TestJob中的count值:3
jobDataMap的count值:3
······
```
#### 4.9.9 cron表达式
**Spring Task框架只能填前6位**
1 2 3 4 5 6 7
**一共七个位置,分别代表:**
1、秒 2、分钟 3、小时 4、日 5、月 6、星期 7、年
**4个常用符号,分别代表 :**
1、/ 从什么开始每过多久执行一次
2、- 从什么时候到什么时候
3、* 通配符
4、 '?'只能在日和星期(周)中指定使用,其作用为不指定
~~**如果激活时间过了没有激活,那后续添加不会运行!!!**~~
#### 4.9.10 整合数据库


### 4.10 整合Kafka消息队列
### 4.11 整合RocketMQ
### 4.12 整合日志框架
#### 4.12.1为什么需要日志框架
首先我们需要明白,日志的作用是什么--即用来在程序运行过程中,将我们需要的信息打印出来,便于我们在调试中查找和观察。在JAVA中存在很多常见的日志框架,如JUL、JCL、Jboss-logging、log4j、logback、slf4j等,这么多日志框架,我们该如何选择?
在日志框架选型之前,我们先了解一个概念,什么是**日志门面**?日志门面,不是**具体的日志解决方案**,它只服务于**各种各样的日志系统**,允许最终用户在部署其应用时使用**其所希望的日志**实现来使用日志功能。而日志实现则是基于对应的日志门面的规范来实现的具体日志功能的框架,常见的日志门面与日志实现关系如下:
日志门面:
JCL 、SL4FJ、JBoss-logging
日志实现
Log4j、JUL(Java.util.logging)、log4j2、logback
每一种日志框架输出信息的效率也不尽相同,而我们日常开发使用的框架中往往都会引入一个日志框架来辅助输出框架信息,然而框架之间由于历史迭代原因及框架性能等问题,选择的日志框架也不一样,常见的**框架**与**默认选择的日志**系统关系如下:

由于历史迭代原因,**JCL和jboss-logging**日志框架,基本已经很久没有更新了,不太适合作为现在框架的主流选择,那么剩下的选择中**log4j、slf4j**是使用最多的,然而由于**log4j**的输出性能问题,log4j的作者选择重新编写了一个日志门面--**Slf4j**,并且编写了基于Slf4j的日志实现--**logback**,其输出信息的效率远超log4j,解决了log4j遗留下的性能问题,所以在**SpringBoot**框架中,默认也选择了**Slf4j**来作为默认日志框架
#### 4.12.2使用
```java
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.trace("这是trace日志。。。"); system.out.print
logger.debug("这是debug日志。。。");
//Springboot默认只到info及以下
logger.info("这是info日志。。。");
logger.warn("这是warn日志。。。");
logger.error("这是error日志。。。");
```

每个日志的实现框架都有自己的配置文件。slf4j门面的**配置文件还是做成日志实现框架自身的配置文件**
#### 4.12.3配置
```yaml
logging:
file:
name: D://log//xxgc.log #文件名 path和name二选一
level:
#日志输出包范围 日志输出级别
com.xxgc.springboot.controller: debug
```
需要做日志大数据分析
```yaml
#yml配置
logging:
config: classpath:log/logback-spring.xml
path: D:/log
level:
root: info
```
在资源文件夹下创建logback-spring.xml
```xml
logback
info
${CONSOLE_LOG_PATTERN}
UTF-8
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${logging.file.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log
${maxFileSize}
${maxHistory}
debug
ACCEPT
DENY
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${logging.file.path}/info/log-info-%d{yyyy-MM-dd}.%i.log
${maxFileSize}
${maxHistory}
info
ACCEPT
DENY
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${logging.file.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
${maxFileSize}
${maxHistory}
warn
ACCEPT
DENY
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${logging.file.path}/error/log-error-%d{yyyy-MM-dd}.%i.log
${maxFileSize}
${maxHistory}
ERROR
ACCEPT
DENY
```
### 4.13 整合RabbitMQ
### 4.14 整合ElasticSearch
### 4.15 整合Solr