Spring缺点:
1)配置繁琐
2)依赖繁琐
SpringBoot:
1)自动配置
SpringBoot的自动配置是一个运行时(更准确的说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不应该用哪个。该过程是SpringBoot自动完成的。
2)起步依赖
起步依赖本质上是一个Maven项目对象模型,定义了对其他库的传递依赖,这些东西加在一起即支持某项目功能
3)辅助功能
提供了一些大型项目中常见的非功能性特征,如嵌入式服务器、安全、指标、健康检查、外部配置。
*总结:SpringBoot提供了一种快速开发Spring项目的方式,而不是对Spring的功能上的增强。
SpringBootg工程搭建:
1)maven方式创建
2)下载插件 Alibaba Cloud Toolkit
下载慢可以配置代理:
JetBrains Marketplace
https://plugins.jetbrains.com 3)Spring Initializr方式创建
SpringBoot起步依赖原理分析:
1)spring-boot-starter-paren
//继承父工程
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
2)spring-boot-starter-web
//导入起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
*总结:
1、在spring-boot-starter-parent中定义了各种技术的版本信息,组合了一套最优搭配的技术版本
2、在各种starter中,定义了完成该功能需要的坐标合集,其中大部分版本信息来自于父工程
3、我们的工程继承parent,引入starter后,通过依赖传递,就可以简单方便获取需要的jar包,并且不会存在版本冲突等问题。
SpringBoot配置
1) 配置文件分类
SpringBoot 是基础约定熟成的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置。
- properties:
server.port=8080
- yml:(: 后要留空格)
server:
port: 8080
#对象(map):键值对的集合
person:
name: zhangsan
person: {name: zhangsan}#行内写法
#数组:一组按次序排列的值
address:
- bejing
- shanghai
address: [beijing,shanghai]#行内写法
#纯量:单个的、不可再分的值
msg1: 'hello \n world' #单引号忽略转义字符
msg2: "hello \n world" #双引号识别转义字符
#参数引用
name:zhgangsan
person:
name: ${name} #引用上边定义的name值
*总结:在同一级目录下优先级为:properties>yml>yaml
2)读取配置文件
1)@Value
@Value("${name}")//name要和配置文件的名字一致
private String name;
@Value("${person.name}")
private String name2;
@Value("${person.age}")
private int age;
@Value("${address[0]}")
private int address1;
@Value("${address[1]}")
private int address1;
2)Environment
@Autowired
private Emvironment env
//获取
System.out.println(env.getProperty('preson.name'));
System.out.println(env.getProperty('address[0]'));
3)@ConfigurationProperties
@ConfigurationProperties(prefix = "person")//前缀指定那个变量
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
3)多环境配置
profile 可以让 Spring 对不同的环境提供不同配置的功能,可以通过激活、指定参数等方式快速切换环境。 换句话说,就是我们需要在不同的场景下使用不同的配置,profile的出现就是要解决我们多环境下切换配置复杂的问题。
在实际开发环境中,我们存在开发环境的配置,部署环境的配置,测试环境的配置等等,里面的配置信息很多时,例如:端口、上下文路径、数据库配置等等,若每次切换环境时,我们都需要进行修改这些配置信息时,会比较麻烦,profile的出现就是为了解决这个问题。
语法规则:application-{profile}.properties(.yaml/.yml)
如果需要创建自定义的的properties文件时,可以用application-xxx.properties/yml的命名方式,这也是官方提供的,其中xxx可以根据自己的需求来定义。
测试案例:创建application-dev.yml和application-prod.yml两个配置文件,指定不同的端口
application-dev.yml
# 开发环境
jdbc:
driverclass: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/java79?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: zhangsan
password: 123456
application-test.yml
# 测试环境
jdbc:
driverclass: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/java79?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: test
password: test
如果需要在两种环境下进行切换,只需要在application.yml中加入如下内容即可
spring:
profiles:
active: dev
常用环境
application.properties:主配置文件 application-dev.properties:开发环境配置文件 application-test.properties:测试环境配置文件 application-prod.properties:生产环境配置文件
SpringBoot热部署:
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>runtime</scope>
</dependency>
添加热部署配置:
spring:
#热部署配置
devtools:
restart:
enabled: true #设置开启热部署
additional-paths: src/main/java #重启目录
exclude: WEB-INF/**
freemarker:
cache: false #页面不加载缓存修改及时生效
idea 添加配置 File -> settings ->Compiler 将Bulid project automatically 勾选上。
Ctrl + Shift + Alt + / 快捷键 弹出Maintenance窗口,选择Registry选项
找到key为compiler.automake.allow.when.app.running 将其勾选
重启项目即可
整合Swagger3:
1)导入依赖
<!--文档框架 springboot2 + Openapi3-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
2)配置swagger
# 文档框架配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
show-extensions: true
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
paths-to-match: '/**'
packages-to-scan: com.xxgc.demo.controller #包扫描路径
default-flat-param-object: false
knife4j:
enable: true
setting:
language: zh_cn
swagger-model-name: 实体类列表
basic:
enable: true #进入文档需要输入密码
username: admin
password: 123456
3)创建SwaggerConfig.java
package com.xxgc.demo.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.HeaderParameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
/**
* @Author:SJY
* @Date :2023/9/5 - 09 - 05 - 16:35
* thanksjava@qq.com
*/
@Configuration
public class SwaggerConfig {
/**
* 根据@Tag 上的排序,写入x-order
*
* @return the global open api customizer
*/
/* @Bean
public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() {
return openApi -> {
if (openApi.getTags()!=null){
openApi.getTags().forEach(tag -> {
Map<String,Object> map=new HashMap<>();
map.put("x-order", RandomUtil.randomInt(0,100));
tag.setExtensions(map);
});
}
if(openApi.getPaths()!=null){
openApi.addExtension("x-test123","333");
openApi.getPaths().addExtension("x-abb",RandomUtil.randomInt(1,100));
}
};
}*/
@Bean
public GroupedOpenApi userApi(){
String[] paths = { "/**" };
String[] packagedToMatch = { "com.xiaominfo.knife4j.demo.web" };
return GroupedOpenApi.builder().group("用户模块")
.pathsToMatch(paths)
.addOperationCustomizer((operation, handlerMethod) -> {
return operation.addParametersItem(new HeaderParameter().name("groupCode").example("测试").description("集团code").schema(new StringSchema()._default("BR").name("groupCode").description("集团code")));
})
.packagesToScan(packagedToMatch).build();
}
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("XXX用户系统API")
.version("1.0")
.description( "Knife4j集成springdoc-openapi示例")
.termsOfService("http://doc.xiaominfo.com")
.license(new License().name("Apache 2.0")
.url("http://doc.xiaominfo.com"))
).addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION))
.components(new Components().addSecuritySchemes(HttpHeaders.AUTHORIZATION,new SecurityScheme()
.name(HttpHeaders.AUTHORIZATION).type(SecurityScheme.Type.HTTP).scheme("bearer")));
}
}
4)常用注解:
@Api(tags = "类上的描述")
@ApiOperation(value = "方法的描述", notes = "接口的描述")
@ApiModel(value = "模型的描述", description = "")
@ApiModelProperty("字段的描述")
@Operation(summary = "获取信息测试",description = "测试用")
@Parameters({
@Parameter(name = "uname",description = "用户名",required = true,example = "admin"),
@Parameter(name = "pword",description = "密码",required = true,example = "123"),
})
@Schema(description = "系统内部状态码")
基于Lombok的多种用法:
1)导入依赖
<!--lombok 自动生成set、get、有参、无参、toString等常用方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
2)下载插件
LomBok
3)添加注解
/**
* 项目统一返回结果对象
* lombok 自动生成set get方法
* @Data set+get+tostring 方法
* @AllArgsConstructor 全参构造方法
* @NoArgsConstructor 无参构造方法
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
@Schema(description = "系统内部状态码")
private int code;
@Schema(description = "系统信息")
private String msg;
@Schema(description = "返回数据")
private T data;
基于HuTool进行数据脱敏
1)导入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
2)参考文档
HutoolHutool 官方文档https://doc.hutool.cn/
统一参数返回
1.封装自定义参数返回类:
package com.xgd.demo.controller.result;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 项目统一返回结果对象
* lombok 自动生成set get方法
* @Data set+get+tostring 方法
* @AllArgsConstructor 全参构造方法
* @NoArgsConstructor 无参构造方法
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
//如果值为空,在转json时就移除
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result<T> {
@Schema(description = "系统内部状态码")
private int code;
@Schema(description = "系统信息")
private String msg;
@Schema(description = "返回数据")
private T data;
//--------------改变部分信息-----------------
public Result msg(String msg){
this.msg = msg;
return this;
}
public Result code(int code){
this.code = code;
return this;
}
//请求成功的返回
public static <T> Result<T> ok(T data){
return new Result(200,"请求成功",data);
}
//请求成功的返回 不带参数
public static <T> Result<T> ok(){
return new Result(200,"请求成功",null);
}
//请求失败的返回 不带参数
public static <T> Result<T> error(){
return new Result(500,"请求失败",null);
}
//请求失败的返回 异常拦截使用
public static <T> Result<T> error(int code,T data){
return new Result(code,"请求失败",data);
}
}
全局异常处理
1)创建全局异常处理类:GlobalExceptionHandler
2)编写异常处理类型
package com.xxgc.demo.controller.error;
import com.xxgc.demo.controller.result.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: SJY
* @Date :2023/9/8 - 09 - 08 - 14:37
* 全局异常拦截器 @ControllerAdvice
*/
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
//参数校验的拦截
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<Result> handleValidatedException(Exception e) {
Result<List<String>> result = null;
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
List<String> errorMessages = ex.getBindingResult().getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.toList());
result = Result.error(HttpStatus.BAD_REQUEST.value(), errorMessages);
} else if (e instanceof ConstraintViolationException) {
ConstraintViolationException ex = (ConstraintViolationException) e;
List<String> errorMessages = ex.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
result = Result.error(HttpStatus.BAD_REQUEST.value(), errorMessages);
} else if (e instanceof BindException) {
BindException ex = (BindException) e;
List<String> errorMessages = ex.getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.toList());
result = Result.error(HttpStatus.BAD_REQUEST.value(), errorMessages);
}
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
//参数转换
@ExceptionHandler(value = {HttpMessageNotReadableException.class})
public ResponseEntity<Result> httpMessageNotReadableException(Exception e) {
return new ResponseEntity<>(Result.error().msg("参数类型转换失败").errorMsg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
/**------------------------ 一定写在最下面 ----------------------------------**/
/**------------------@TODO 开发环境和测试环境不用 只在生产环境用 留坑 (虚竹)-------------------**/
//运行时的异常
@ExceptionHandler(value = {Exception.class})
public ResponseEntity<Result> runException(Exception e) {
return new ResponseEntity<>(Result.error().msg("当前服务器压力大,请稍后重试"), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
常用参数校验注解
@Valid: 该注解用于指示一个参数或请求体需要进行验证。可以将该注解添加到参数上,表示该参数需要进行验证。
@NotNull: 该注解用于指示一个参数不能为null。如果参数值为null,则会抛出一个异常。
@NotEmpty: 该注解用于指示一个参数不能为空。如果参数值为空,则会抛出一个异常。
@Size(min=, max=): 该注解用于指示一个参数的长度必须在指定范围内。其中,min表示参数长度的最小值,max表示参数长度的最大值。
@Pattern(regex=, flags=): 该注解用于指示一个参数的值必须匹配指定的正则表达式。其中,regex表示正则表达式,flags表示正则表达式的标志。
@Min(value=): 该注解用于指示一个参数的值必须大于等于指定的最小值。
@Max(value=): 该注解用于指示一个参数的值必须小于等于指定的最大值。
@DecimalMin(value=): 该注解用于指示一个参数的值必须大于等于指定的最小十进制值。
@DecimalMax(value=): 该注解用于指示一个参数的值必须小于等于指定的最大十进制值。
@Email(message=): 该注解用于指示一个参数的值必须是一个有效的电子邮件地址。如果参数值不是有效的电子邮件地址,则会抛出一个异常,并且可以在message中指定异常消息。
@EqualTo(referringParamName=): 该注解用于指示一个参数的值必须等于另一个参数的值。其中,referringParamName表示另一个参数的名称。
自定义注解
1)拦截器方式:
2)切面AOP方式:
导入依赖:
<!--SpringAOP的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建一个自定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckIdCard {
}
创建一个Aspect来处理这个注解:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@Aspect
@Component
public class IdCardInterceptor {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
@Around("execution(* com.yourpackage..*(..)) && args(idCard)")'execution(* com.yourpackage..*(..))' 是一个方法匹配器,表示这个环绕建议应用于com.yourpackage包及其子包中所有的方法。'args(idCard)' 是一个参数匹配器,表示这个环绕建议只应用于含有idCard参数的方法
//@Around("@annotation(CheckIdCard)")表示这个方法会在带有CheckIdCard注解的方法被调用之前执行
public Object checkIdCard(ProceedingJoinPoint joinPoint, String idCard) throws ParseException {
try {
Date birthDate = sdf.parse(idCard.substring(6, 14)); // 假设身份证号的出生日期在从左起第6到14位
Date currentDate = new Date();
if (currentDate.after(birthDate)) {
throw new RuntimeException("身份证号出生日期不能在当前日期之前");
}
} catch (ParseException e) {
throw new RuntimeException("身份证号格式不正确", e);
}
return joinPoint.proceed();
}
}
3)巧用ConstraintValidator接口方式:
创建自定义注解类:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Documented
@Constraint(validatedBy = {IdCardBirthdayBeforeCurrentDateValidator.class})//用于指定一个或多个验证器来验证类或字段的约束。在这个例子中,它指定了 IdNoValidator 类作为验证器来验证该类或字段的约束。具体来说,它可以用于验证身份证号码、账号、手机号码等各种类型的标识号码。具体的验证逻辑需要在 IdNoValidator 类中实现。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IdCardBirthdayBeforeCurrentDate {
String message() default "身份证号码的出生日期不能在当前日期之前";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String regexp() default "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
}
在这个注解类中,我们定义了一个regexp属性,用于存储身份证号码的正则表达式。这个正则表达式可以匹配所有的有效身份证号码,包括出生日期在当前日期之前的身份证号码。
创建一个自定义验证器类,该类需要实现ConstraintValidator接口。在这个类中,您可以实现isValid方法,用于检查身份证号码的出生日期是否在当前日期之前。
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IdCardBirthdayBeforeCurrentDateValidator implements ConstraintValidator<IdCardBirthdayBeforeCurrentDate, String> {
private String regexp;
@Override
public void initialize(IdCardBirthdayBeforeCurrentDate constraintAnnotation) {
this.regexp = constraintAnnotation.regexp();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isEmpty()) {
return true;
}
Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(value);
if (!matcher.matches()) {
return false;
}
//int year = Integer.parseInt(idNo.substring(6, 10));
//int month = Integer.parseInt(idNo.substring(10, 12));
//int day = Integer.parseInt(idNo.substring(12, 14));
// Calendar calendar = Calendar.getInstance();
//calendar.set(year, month, day);
//return calendar.getTime().before(new Date());
String birthdayStr = value.substring(6, 14);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate birthday = LocalDate.parse(birthdayStr, formatter);
LocalDate currentDate = LocalDate.now();
return birthday.isBefore(currentDate);
}
}
在这个验证器类中,我们实现了isValid方法,用于检查身份证号码的出生日期是否在当前日期之前。我们首先检查身份证号码是否符合正则表达式。如果不符合,则返回false。如果符合,则将出生日期转换为LocalDate类型,并与当前日期进行比较。如果出生日期在当前日期之前,则返回true,否则返回false。
使用@IdCardBirthdayBeforeCurrentDate注解来指定身份证号码的正则表达式
import javax.validation.constraints.Pattern;
public class User {
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$", message = "身份证号码的出生日期不能在当前日期之前")
private String idCard;
// getters and setters
}
ConstraintValidator介绍:
1)ConstraintValidator是Java中的一种工具类,用于做实体类字段校验
2)ConstraintValidator
接口是Hibernate Validator库中的一个重要组件,用于实现自定义的验证规则。这些规则可以应用于Java Bean中的字段,以进行各种复杂的数据验证。
3)ConstraintValidator
接口有两个主要方法:initialize
和isValid
。
initialize
方法:此方法在验证器创建时被调用,可以用于执行任何必要的初始化工作,例如,加载配置信息、初始化状态等。isValid
方法:此方法用于执行实际的验证逻辑。它接收两个参数:一个是待验证的实体对象(即要进行验证的Java Bean),另一个是ConstraintValidatorContext
对象,它可以提供额外的上下文信息,如字段名称、字段索引等。通过这个方法,你可以实现自定义的验证逻辑,例如检查数据是否满足特定的格式、范围、业务规则等
整合JWT
1)导入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2)如何使用
JWT工具-JWTUtil | HutoolHutool 官方文档https://doc.hutool.cn/pages/JWTUtil/#%E4%BD%BF%E7%94%A8
整合mybatis-plus
1)添加mybatis-plus依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>
2)导入jdbc的mysql驱动依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
3)配置application.yml
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# MyBatis Plus配置
mybatis-plus:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
configLocation:
classpath:mybatis/mybatis-config.xml # 加载全局的配置文件
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #是否打印sql语句
整合Redis
1)导入依赖
<!-- redis 缓存操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2) 配置application.yml
# redis 配置
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
3)新建redis.config配置类,配置模板
@Configuration
public class RedisConfig {
@Bean
public RedisSerializer<Object> redisSerializer() {
//创建JSON序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//解决localDateTime的序列化问题
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//必须设置,否则无法将JSON转化为对象,会转化成Map类型
objectMapper.disableDefaultTyping();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置Redis缓存有效期为1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<Object> serializer = redisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
3)新建RedisUtil.java,创建工具类
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
// =============================Common 基础============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
// 如果返回值为 null,则返回 0L
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String 字符串=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time,
TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ===============================List 列表=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0
* 时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return 赋值结果
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return 赋值结果
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return 赋值结果
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ============================Set 集合=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
return redisTemplate.opsForSet().remove(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ================================Hash 哈希=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
}
4)使用redis
//web层 打通 service层
@Autowired
private IDemoUsersService iDemoUsersService;
@Autowired
private RedisUtil redisUtil;//注入redis工具类
@GetMapping("/getUserInfoById")
public Result getUserInfoById(Long userId){
String key = "user"+userId;
//缓存是否有这个对象
boolean b = redisUtil.hasKey(key);
if(b){//如果有
log.info("我是缓存中获取的");
DemoUsers demoUsers = (DemoUsers) redisUtil.get(key);
return Result.ok(demoUsers);
}else{//如果缓存没有
log.info("我是数据库获取的");
DemoUsers demoUsers = iDemoUsersService.getById(userId);
//往缓存中丢一份
redisUtil.set(key,demoUsers);
return Result.ok(demoUsers);
}
}
5)相关注解:
@cacheable(cacheNames="userInfo",key="#userId",unless="#userId == 1",condition="#userId == 1")
//缓存组件名、 key名、 userId=1 时不使用缓存、userId=2 时使用缓存