IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> newspringboot -> 正文阅读

[Java知识库]newspringboot

spring boot原理

自动配置:

pom.xml

  • 在写入或者引入一些springboot依赖的时候,不需要指定版本,因为有父项目指定的版本。

启动器:

  • <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    
  • 上面也就是springboot的启动器

  • spring-boot-starter-web也就是针对web的启动器,默认会导入web的依赖或者配置

  • springboot会将所有的功能场景变成一个个的容器

  • 我们要使用什么功能,就只需要找到对应的控制器就行starter

主程序:

  1. @SpringBootApplication标注这个类是springboot的应用

  2. @SpringBootConfiguration   //springboot的配置
         @Configuration  //spring的配置
             @Component   //说明这是一个spring的组件
    
    @EnableAutoConfiguration  //自动配置
          @AutoConfigurationPackage   //自动配置包
               @Import(AutoConfigurationPackages.Registrar.class)  //导入选择器
    

1.yaml

? Spring Boot 提供了大量的自动配置,极大地简化了spring 应用的开发过程,当用户创建了一个 Spring Boot 项目后,即使不进行任何配置,该项目也能顺利的运行起来。当然,用户也可以根据自身的需要使用配置文件修改 Spring Boot 的默认设置。

SpringBoot 默认使用以下 2 种全局的配置文件,其文件名是固定的。

  • application.properties
  • application.yml

1.yaml简介

? YAML 全称 YAML Ain’t Markup Language,它是一种以数据为中心的标记语言,比 XML 和 JSON 更适合作为配置文件。

? 想要使用 YAML 作为属性配置文件(以 .yml 或 .yaml 结尾),需要将 SnakeYAML 库添加到 classpath 下,Spring Boot 中的 spring-boot-starter-web 或 spring-boot-starter 都对 SnakeYAML 库做了集成, 只要项目中引用了这两个 Starter 中的任何一个,Spring Boot 会自动添加 SnakeYAML 库到 classpath 下。

2.YAML 语法

YAML 的语法如下:

  • 使用缩进表示层级关系。
  • 缩进时不允许使用 Tab 键,只允许使用空格。
  • 缩进的空格数不重要,但同级元素必须左侧对齐。
  • 大小写敏感。

例如:

user:
  id: 1
  name: 我的世界
  age: 20
  map: {name: 我的世界,age: 1}

3.yaml常用写法

YAML 支持以下三种数据结构:

  • 对象:键值对的集合
  • 数组:一组按次序排列的值
  • 字面量:单个的、不可拆分的值

1.yaml字面量写法

字面量是指单个的,不可拆分的值,例如:数字、字符串、布尔值、以及日期等。

在 YAML 中,使用“key:[空格]value**”**的形式表示一对键值对(空格不能省略),如 name: hello;

字面量直接写在键值对的“value**”**中即可,且默认情况下字符串是不需要使用单引号或双引号的。

  1. 若字符串使用单引号,则会转义特殊字符。
  name: '你好呀\n你好'

输出:

'你好呀\n你好'

  1. 若字符串使用双引号,则不会转义特殊字符,特殊字符会输出为其本身想表达的含义
  name: "你好呀\n你好"

输出

name='你好呀 你好'

2.yaml对象写法

在 YAML 中,对象可能包含多个属性,每一个属性都是一对键值对。

YAML 为对象提供了 2 种写法:

  1. 普通写法,使用缩进表示对象与属性的层级关系。
website: 
  name: hello
  url: www.xxx.com
  1. 行内写法:
website: {name: hello,url: www.xxx.com}

3.YAML 数组写法

YAML 使用“-”表示数组中的元素,普通写法如下:

pets:
  -dog
  -cat
  -pig

行内写法:

pets: [dog,cat,pig]

4.复合结构

以上三种数据结构可以任意组合使用,以实现不同的用户需求,例如:

person:
  name: zhangsan
  age: 30
  pets:
    -dog
    -cat
    -pig
  car:
    name: QQ
  child:
    name: zhangxiaosan
    age: 2

5.yaml组织结构

一个 YAML 文件可以由一个或多个文档组成,文档之间使用“—**”作为分隔符,且个文档相互独立,互不干扰。如果 YAML 文件只包含一个文档,则“—”**分隔符可以省略。

---
website:
  name: bianchengbang
  url: www.biancheng.net
---
website: {name: bianchengbang,url: www.biancheng.net}
pets:
  -dog
  -cat
  -pig
---
pets: [dog,cat,pig]
name: "zhangsan \n lisi"
---
name: 'zhangsan \n lisi'

2.springboot配置绑定

? 所谓“配置绑定”就是把配置文件中的值与 JavaBean 中对应的属性进行绑定。通常,我们会把一些配置信息(例如,数据库配置)放在配置文件中,然后通过 Java 代码去读取该配置文件,并且把配置文件中指定的配置封装到 JavaBean(实体类) 中。

SpringBoot 提供了以下 2 种方式进行配置绑定:

  • 使用 @ConfigurationProperties 注解
  • 使用 @Value 注解

1.@ConfigurationProperties

? 通过 Spring Boot 提供的 @ConfigurationProperties 注解,可以将全局配置文件中的配置数据绑定到 JavaBean 中。下面我们以 Spring Boot 项目 helloworld 为例,演示如何通过 @ConfigurationProperties 注解进行配置绑定。

  1. 在application.yaml中写入
parse:
  name: 你好呀你好
  age: 20
  list:
    - 电脑
    - 游戏
    - 学习
  1. 在一个java实体类中写入
/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
*
* @ConfigurationProperties:告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "parse":配置文件中哪个下面的所有属性进行一一映射
*
* 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能;
*/
@Component
@ConfigurationProperties(prefix = "parse")
public class User {
    private  String name;
    private  Integer age;
    private List<String>  list;
    //其他方法省略
}

注意:

  • 只有在容器中的组件,才会拥有 SpringBoot 提供的强大功能。如果我们想要使用 @ConfigurationProperties 注解进行配置绑定,那么首先就要保证该对 JavaBean 对象在 IoC 容器中,所以需要用到 @Component 注解来添加组件到容器中。
  • JavaBean 上使用了注解 @ConfigurationProperties(prefix = “parse”) ,它表示将这个 JavaBean 中的所有属性与配置文件中以“parse”为前缀的配置进行绑定。

2.@Value

当我们只需要读取配置文件中的某一个配置时,可以通过 @Value 注解获取。

  1. 利用@Value进行注入值
@Component
public class User {
    @Value("${parse.name}")
    private  String name;
    @Value("${parse.age}")
    private  Integer age;
    private List<String>  list;
    //省略其他方法
}

注意:

  • 如果是上面集合形式的,不能使用value注解注入

3.@Value 与 @ConfigurationProperties 对比

@Value 和 @ConfigurationProperties 注解都能读取配置文件中的属性值并绑定到 JavaBean 中,但两者存在以下不同。

使用位置不同

  • @ConfigurationProperties:标注在 JavaBean 的类名上;
  • @Value:标注在 JavaBean 的属性上。

功能不同

  • @ConfigurationProperties:用于批量绑定配置文件中的配置;
  • @Value:只能一个一个的指定需要绑定的配置。

松散绑定支持不同

@ConfigurationProperties:支持松散绑定(松散语法),例如实体类 Person 中有一个属性为 firstName,那么配置文件中的属性名支持以下写法:

  • person.firstName
  • person.first-name
  • person.first_name
  • PERSON_FIRST_NAME

@Vaule:不支持松散绑定。

SpEL 支持不同

  • @ConfigurationProperties:不支持 SpEL 表达式;
  • @Value:支持 SpEL 表达式。

复杂类型封装

  • @ConfigurationProperties:支持所有类型数据的封装,例如 Map、List、Set、以及对象等;
  • @Value:只支持基本数据类型的封装,例如字符串、布尔值、整数等类型。

应用场景不同

@Value 和 @ConfigurationProperties 两个注解之间,并没有明显的优劣之分,它们只是适合的应用场景不同而已。

  • 若只是获取配置文件中的某项值,则推荐使用 @Value 注解;
  • 若专门编写了一个 JavaBean 来和配置文件进行映射,则建议使用 @ConfigurationProperties 注解。

我们在选用时,根据实际应用场景选择合适的注解能达到事半功倍的效果。

4.@PropertySource

如果将所有的配置都集中到 application.properties 或 application.yml 中,那么这个配置文件会十分的臃肿且难以维护,因此我们通常会将与 Spring Boot 无关的配置(例如自定义配置)提取出来,写在一个单独的配置文件中,并在对应的 JavaBean 上使用 @PropertySource 注解指向该配置文件。

下面在resource下创建一个person.properties配置文件,里面写的内容不能与默认的properties的文件或者yaml文件内容重复,或者删除默认里面的文件内容。

student.name=我的世界
student.age=20
student.list=玩耍,电脑,跑步

创建Java实体类

@Component
@PropertySource(value = "classpath:person.properties")
/*
   再导入其他配置文件后,可以不使用ConfigurationProperties注解绑定值,
   可以使用@Value以点的形式指向属性,但是数组,集合不能使用
*/
@ConfigurationProperties(prefix = "student") 
public class User {
  private  String name;
  private  Integer age;
  private List<String>  list;
}

3.JSR303校验

导入springboot依赖

<!--JSR303校验-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

注意: 如果要使用JSR303校验,则必须在要使用的类上添加@Validated注解,表示这个类支持JSR303校验,否则即使在类属性上使用了JSR303的校验注解也是没有用的

JSR303校验的注解规则:

空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
 
 
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
 
 
 
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
 
 
 
 
日期检查
@Past 验证 DateCalendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 DateCalendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
 
 
 
 
数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integernull
@Min 验证 NumberString 对象是否大等于指定的值
@Max 验证 NumberString 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 NumberString 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

4.默认配置文件及文件位置

通常情况下,Spring Boot 在启动时会将 resources 目录下的 application.properties 或 apllication.yml 作为其默认配置文件,我们可以在该配置文件中对项目进行配置,但这并不意味着 Spring Boot 项目中只能存在一个 application.properties 或 application.yml。

1.默认配置文件

Spring Boot 项目中可以存在多个 application.properties 或 apllication.yml。

Spring Boot 启动时会扫描以下 2个位置的 application.properties 或 apllication.yml 文件,并将它们作为 Spring boot 的默认配置文件。

  1. classpath:/config/ 也就是在java或者resource目录下创建
  2. classpath:/ 也就是在java或者resource目录下创建

5. Profile(多环境配置)

在实际的项目开发中,一个项目通常会存在多个环境,例如,开发环境、测试环境和生产环境等。不同环境的配置也不尽相同,例如开发环境使用的是开发数据库,测试环境使用的是测试数据库,而生产环境使用的是线上的正式数据库。

Profile 为在不同环境下使用不同的配置提供了支持,我们可以通过激活、指定参数等方式快速切换环境。

1.多Profile 文件形式

Spring Boot 的配置文件共有两种形式:.properties 文件和 .yml 文件,不管哪种形式,它们都能通过文件名的命名形式区分出不同的环境的配置,文件命名格式为:

application-{profile}.properties/yml

其中,{profile} 一般为各个环境的名称或简称,例如 dev、test 和 prod 等等。

properties 配置

在项目的src/main/resources新建四个文件

  • application.properties:主配置文件
  • application-dev.properties:开发环境配置文件
  • application-test.properties:测试环境配置文件
  • application-prod.properties:生产环境配置文件

在application.properties文件中,指定默认服务器端口号为 8080,并通过以下配置激活生产环境(prod)的 profile。

# 指定默认端口号
server.port=8080

# 激活指定的profiles文件
spring.profiles.active=test

在 application-dev.properties 中,指定开发环境端口号为 8081,配置如下

# 开发环境
server.port=8081

在 application-test.properties 中,指定测试环境端口号为 8082,配置如下。

# 测试环境
server.port=8082

在 application-prod.properties 中,指定生产环境端口号为 8083,配置如下。

# 生产环境
server.port=8083

即使主配置文件中设置了端口号,但是在主配置文件中设置profile,所以就会使用profile指向的配置文件了,也可以使用yaml后缀的文件。

2.多Profile 文档块模式

在 YAML 配置文件中,可以使用“—”把配置文件分割成了多个文档块,因此我们可以在不同的文档块中针对不同的环境进行不同的配置,并在第一个文档块内对配置进行切换。

在application.yaml文件中配置多文档快吗模式

# 默认配置
server:
  port: 8080
#更改配置为dev
spring:
  profiles:
    active: dev
---
#开发环境
server:
  port: 8081
spring:
  profiles: dev

---
#测试环境
server:
  port: 8082
spring:
  profiles: test

---
#生产环境
server:
  port: 8083
spring:
  profiles: prod

**注意:**上面的配置是可以进行更换配置,但是不是最优的方案,可以使用下面的配置

# 默认配置
server:
  port: 8080
#更换配置
spring:
  profiles:
    active: prod
---
# 开发配置
server:
  port: 8081
spring:
  config:
    activate:
      on-profile: dev

---
# 测试环境
server:
  port: 8082
spring:
  config:
    activate:
      on-profile: test

---
#生产环境
server:
  port: 8083
spring:
  config:
    activate:
      on-profile: prod

6.springboot Web开发

要解决的问题

  • 导入静态资源
  • 解决首页的订制
  • 模板引擎 Thymeleaf
  • 装备扩展springMvc
  • 增删改查
  • 拦截器
  • 国际化

1.静态资源导入问题

在springboot中,静态资源文件存放在resources目录下,其中包含public目录,static目录,resources目录,

按优先级排序:resources>static>public

根据springboot底层描述,可以设置自己的资源目录,需要在配置文件中设置

spring:
  mvc:
    static-path-pattern: #自己的资源目录

**注意:**虽然可以设置自己的资源目录,但是默认的目录文件可以支撑我们的开发,所以没必要跟换。

2.首页定制

静态资源文件夹下的所有 index.html 被称为静态首页或者欢迎页,它们会被被 /** 映射,换句话说就是,当我们访问“/”或者“/index.html”时,都会跳转到静态首页(欢迎页)。

在springboot的项目中,所有的网页文件都已html结尾。

  1. 在没有使用模板引擎的情况下定制首页

我们需要在静态资源的目录下,任何一个目录创建一个index.html文件,但不要在templates下创建,因为这个目录需要在模板引擎下创建才会生效。

  1. 使用模板引擎创建首页,其实也就是访问/就会跳转

3.模板引擎Thymeleaf

Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。它与 JSP,Velocity,FreeMaker 等模板引擎类似,也可以轻易地与 Spring MVC 等 Web 框架集成。与其它模板引擎相比,Thymeleaf 最大的特点是,即使不启动 Web 应用,也可以直接在浏览器中打开并正确显示模板页面 。

Thymeleaf 是新一代 Java 模板引擎,与 Velocity、FreeMarker 等传统 Java 模板引擎不同,Thymeleaf 支持 HTML 原型,其文件后缀为“.html”,因此它可以直接被浏览器打开,此时浏览器会忽略未定义的 Thymeleaf 标签属性,展示 thymeleaf 模板的静态页面效果;当通过 Web 应用程序访问时,Thymeleaf 会动态地替换掉静态内容,使页面动态显示。

Thymeleaf 特点

  • 动静结合:Thymeleaf 既可以直接使用浏览器打开,查看页面的静态效果,也可以通过 Web 应用程序进行访问,查看动态页面效果。
  • 开箱即用:Thymeleaf 提供了 Spring 标准方言以及一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
  • 多方言支持:它提供了 Thymeleaf 标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、 OGNL 表达式;必要时,开发人员也可以扩展和创建自定义的方言。
  • 与 SpringBoot 完美整合:SpringBoot 为 Thymeleaf 提供了的默认配置,并且还为 Thymeleaf 设置了视图解析器,因此 Thymeleaf 可以与 Spring Boot 完美整合。

Spring Boot 推荐使用 Thymeleaf 作为其模板引擎。SpringBoot 为 Thymeleaf 提供了一系列默认配置,项目中一但导入了 Thymeleaf 的依赖,相对应的自动配置 (ThymeleafAutoConfiguration) 就会自动生效,因此 Thymeleaf 可以与 Spring Boot 完美整合 。

引入依赖

<!--Thymeleaf 启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

声明空间

在使用Thymeleaf之间,还需要在HTML标签中声明名称空间,示例代码如下:

<html lang="en" xmlns:th="http://www.thymeleaf.org">

在 html 标签中声明此名称空间,可避免编辑器出现 html 验证错误,但这一步并非必须进行的,即使我们不声明该命名空间,也不影响 Thymeleaf 的使用。

4.定制和扩展springMvc

Spring Boot 抛弃了传统 xml 配置文件,通过配置类(标注 @Configuration 的类,相当于一个 xml 配置文件)以 JavaBean 形式进行相关配置。

Spring Boot 对 Spring MVC 的自动配置可以满足我们的大部分需求,但是我们也可以通过自定义配置类(标注 @Configuration 的类)并实现 WebMvcConfigurer 接口来定制 Spring MVC 配置,例如拦截器、格式化程序、视图控制器等等。

注意: 1.5 及以前是通过继承 WebMvcConfigurerAdapter 抽象类来定制 Spring MVC 配置的,但在 SpringBoot 2.0 后,WebMvcConfigurerAdapter 抽象类就被弃用了,改为实现 WebMvcConfigurer 接口来定制 Spring MvVC 配置。

WebMvcConfigurer 是一个基于 Java 8 的接口,该接口定义了许多与 Spring MVC 相关的方法,其中大部分方法都是 default 类型的,且都是空实现。因此我们只需要定义一个配置类实现 WebMvcConfigurer 接口,并重写相应的方法便可以定制 Spring MVC 的配置。

方法说明
configurePathMatchHandlerMappings 路径的匹配规则。
configureContentNegotiation内容协商策略(一个请求路径返回多种数据格式)。
configureAsyncSupport配置异步请求处理相关参数,处理异步请求
configureDefaultServletHandling配置是否需要以下功能:如果一个请求没有被任何Handler处理,那是否使用DefaultServletHttpRequestHandler来进行处理?
addFormatters增加额外的Converter和Formatter, 添加格式化器或者转化器。
addInterceptors添加 Spring MVC 生命周期拦截器,对请求进行拦截处理。
addResourceHandlers添加或修改静态资源(例如图片,js,css 等)映射; Spring Boot 默认设置的静态资源文件夹就是通过重写该方法设置的。
addCorsMappings配置跨域请求相关参数
addViewControllers主要用于实现无业务逻辑跳转,例如主页跳转,简单的请求重定向,错误页跳转等
configureViewResolvers配置视图解析器,将 Controller 返回的字符串(视图名称),转换为具体的视图进行渲染。
addArgumentResolvers添加解析器以支持自定义控制器方法参数类型,实现该方法不会覆盖用于解析处理程序方法参数的内置支持;
要自定义内置的参数解析支持, 同样可以通过 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。
addReturnValueHandlers添加处理程序来支持自定义控制器方法返回值类型。使用此选项不会覆盖处理返回值的内置支持;
要自定义处理返回值的内置支持,请直接配置 RequestMappingHandlerAdapter。
configureMessageConverters用于配置默认的消息转换器(转换 HTTP 请求和响应)。
extendMessageConverters直接添加消息转换器,会关闭默认的消息转换器列表;
实现该方法即可在不关闭默认转换器的起提下,新增一个自定义转换器。
configureHandlerExceptionResolvers配置异常解析器。
extendHandlerExceptionResolvers扩展或修改默认的异常解析器列表。

扩展springmvc

如果 Spring Boot 对 Spring MVC 的自动配置不能满足我们的需要,我们还可以通过自定义一个 WebMvcConfigurer 类型(实现 WebMvcConfigurer 接口)的配置类(标注 @Configuration,但不标注 @EnableWebMvc 注解的类),来扩展 Spring MVC。这样不但能够保留 Spring Boot 对 Spring MVC 的自动配置,享受 Spring Boot 自动配置带来的便利,还能额外增加自定义的 Spring MVC 配置。

全面接管springmvc

在一些特殊情况下,我们可能需要抛弃 Spring Boot 对 Spring MVC 的全部自动配置,完全接管 Spring MVC。此时我们可以自定义一个 WebMvcConfigurer 类型(实现 WebMvcConfigurer 接口)的配置类,并在该类上标注 @EnableWebMvc 注解,来实现完全接管 Spring MVC。

注意:完全接管 Spring MVC 后,Spring Boot 对 Spring MVC 的自动配置将全部失效。

我们知道,Spring Boot 能够访问位于静态资源文件夹中的静态文件,这是在 Spring Boot 对 Spring MVC 的默认自动配置中定义的,当我们全面接管 Spring MVC 后,Spring Boot 对 Spring MVC 的默认配置都会失效,此时再访问静态资源文件夹中的静态资源就会报 404 错误。

7.springboot拦截器

我们对拦截器并不陌生,无论是 Struts 2 还是 Spring MVC 中都提供了拦截器功能,它可以根据 URL 对请求进行拦截,主要应用于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能上。Spring Boot 同样提供了拦截器功能。

在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:

  1. 定义拦截器;
  2. 注册拦截器;
  3. 指定拦截规则(如果是拦截所有,静态资源也会被拦截)。

1.定义拦截器

在 Spring Boot 中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口即可。

HandlerInterceptor 接口中定义以下 3 个方法,如下表。

返回类型方法说明
BooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
voidpostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。
voidafterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

2.注册拦截器

创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。

@Configuration
public class loginconfig implements WebMvcConfigurer {
    
    @Bean
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor());
    }
}

3.指定拦截规则

修改 配置类中 addInterceptors() 方法的代码,继续指定拦截器的拦截规则,代码如下。

@Configuration
public class loginconfig implements WebMvcConfigurer {
    ......
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.info("注册拦截器");
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //拦截所有请求,包括静态资源文件
                .excludePathPatterns("/", "/login", "/index.html", "/user/login", "/css/**", "/images/**", "/js/**", "/fonts/**"); //放行登录页,登陆操作,静态资源
    }
}

在指定拦截器拦截规则时,调用了两个方法,这两个方法的说明如下:

  • addPathPatterns:该方法用于指定拦截路径,例如拦截路径为“/**”,表示拦截所有请求,包括对静态资源的请求。
  • excludePathPatterns:该方法用于排除拦截路径,即指定不需要被拦截器拦截的请求。

4.拦截器实现登陆功能

  1. 创建login.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <p th:text="${name}"></p>
        <form method="get"  th:action="@{/login}">
            账号:<input  type="text" name="name"><br>
            密码:<input type="password" name="password"><br>
            <input type="submit"   value="login">
        </form>
    </body>
</html>
  1. 创建主界面main
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>主界面</h1>
    </body>
</html>
  1. 连接器定义
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取session的message的值
        Object name =request.getSession().getAttribute("message");
        if(name==null){
            //如果session为空,则返回到登录页
            response.sendRedirect("/tologin");
            return  false;
        }else{
            return  true;
        }
    }
}
  1. 注册拦截器和扩展springmvc的controller
public class myconfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
                                                       //   /**拦截所有请求
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
             //放行/tologin和/login
            .excludePathPatterns("/tologin","/login");
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //  访问/跳转到main
        registry.addViewController("/").setViewName("main");
        //   访问/tologin跳转到login
        registry.addViewController("/tologin").setViewName("login");
    }
}
  1. 创建一个配置类注入myconfig类
@Configuration
public class loginconfig {

    @Bean
    public  myconfig myconfig(){
        return  new myconfig();
    }
}
  1. 创建controller层
@Controller
public class login {
    @RequestMapping("/login")
    public String login(@RequestParam("name") String name,@RequestParam("password") String password,
                        HttpServletRequest request, HttpSession session,Model model){
        session.setMaxInactiveInterval(5);
        if(name.equals("user")&&password.equals("123")){
            session.setAttribute("message",name);
            return "redirect:/";
        }else {
            model.addAttribute("name","密码错误");
            return  "login";
        }
    }

}

8.整合JDBC

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 都默认采用整合 Spring Data 的方式进行统一处理,通过大量自动配置,来简化我们对数据访问层的操作,我们只需要进行简单的设置即可实现对书层的访问。

1.导入jdbc启动器

Spring Boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的场景启动器(Starter),场景启动器中整合了该场景下各种可能用到的依赖,让用户摆脱了处理各种依赖和配置的困扰。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

2.导入数据库驱动

JDBC 的场景启动器中并没有导入数据库驱动,我们需要根据自身的需求引入所需的数据库驱动。例如,访问 MySQL 数据库时,需要导入 MySQL 的数据库驱动:mysql-connector-java,示例代码如下。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Spring Boot 默认为数据库驱动程序做了版本仲裁,所以我们在导入数据库驱动时,可以不再声明版本。需要注意的是,数据库驱动的版本必须与数据库的版本相对应。

3.配置数据源

在导入了 JDBC 场景启动器和数据库驱动后,接下来我们就可以在配置文件(application.properties/yml)中配置数据源了,示例代码(application.yml)如下。

spring:
  datasource:
    username: 用户 
    password: 密码
    url: jdbc:mysql://localHost:3306/数据库名?useSSL=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver

**注意:**在一些情况下,可能还需要配置时区,否则就会报错

4.测试

@SpringBootTest
class SpringdemoApplicationTests {

    @Autowired
    private  DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        //创建连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        //关闭连接
        connection.close();
    }
}

如果控制台输出 com.mysql.cj.jdbc.ConnectionImpl@3ebe4ccc表示已经成功

5.JdbcTemplate实现增删改查

在controller层实现增删改查

@Controller
@RestController
public class login {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //查询全部数据
    @GetMapping("/getuser")
    public List<Map<String,Object>> userList(){
        String sql="select *  from user.user";
        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql);
        return mapList;
    }
    //增添一条数据
    @GetMapping("/adduser/{id}")
    public int  adduser(@PathVariable Integer id){
        String sql="insert into  user.user  values (?,\"超哥\",'123456')";
        return jdbcTemplate.update(sql,id);
    }

    //根据id修改一条数据
    @GetMapping("/update/{id}")
    public int  updateUser(@PathVariable  Integer id){
        String sql="update user.user set name ='hello world',pwd='258' where  id=?";
        return jdbcTemplate.update(sql,id);
    }

    //删除一条数据
    @GetMapping("/remove/{id}")
    public int  removeuser(@PathVariable Integer  id){
        String sql="delete  from  user.user  where  id=?";
        return  jdbcTemplate.update(sql,id);
    }
}

9.整合Druid数据源

Spring Boot 2.x 默认使用 HikariCP 作为数据源,我们只要在项目中导入了 Spring Boot 的 JDBC 场景启动器,便可以使用 HikariCP 数据源获取数据库连接,对数据库进行增删改查等操作。

HikariCP 是目前市面上性能最好的数据源产品,但在实际的开发过程中,企业往往更青睐于另一款数据源产品:Druid,它是目前国内使用范围最广的数据源产品。

Druid 是阿里巴巴推出的一款开源的高性能数据源产品,Druid 支持所有 JDBC 兼容的数据库,包括 Oracle、MySQL、SQL Server 和 H2 等等。Druid 不仅结合了 C3P0、DBCP 和 PROXOOL 等数据源产品的优点,同时还加入了强大的监控功能。通过 Druid 的监控功能,可以实时观察数据库连接池和 SQL 的运行情况,帮助用户及时排查出系统中存在的问题。

Druid 不是 Spring Boot 内部提供的技术,它属于第三方技术,我们可以通过以下两种方式进行整合:

  • 自定义整合 Druid
  • 通过 starter 整合 Druid

1.引入Druid数据源依赖

同时也要导入jdbc和mysql的驱动

<!--采用自定义方式整合 druid 数据源-->
<!--自定义整合需要编写一个与之相关的配置类-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.9</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

在application.yaml配置数据源

spring:
  datasource:
    username: 用户名
    password: 密码
    url: jdbc:mysql://localHost:3306/数据库?useSSL=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

创建测试类

@SpringBootTest
class SpringdemoApplicationTests {
    @Autowired
    private  DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
    }
}

如果控制台显示class com.alibaba.druid.pool.DruidDataSource表示使用的是druid数据源

2.druid后台监控

spring:
  datasource:
    username: 用户名
    password: 密码
    url: jdbc:mysql://localHost:3306/数据库
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
#自动往数据库建表
#    schema:
#      - classpath:department.sql

    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
#   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

上面除了jdbc的默认配置,其他配置也就是druid的配置。

创建druid的配置类,将spring.datasource导入到druid数据源中

@Configuration
public class myconfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }
    //因为Springboot内置了servlet容器,所以没有web.xml,替代方法就是将ServletRegistrationBean注册进去
    //加入后台监控
    @Bean  //这里其实就相当于servlet的web.xml
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean =
            new ServletRegistrationBean<StatViewServlet>(new StatViewServlet(),"/druid/*");

        //后台需要有人登录,进行配置
        //bean.addUrlMappings(); 这个可以添加映射,我们在构造里已经写了
        //设置一些初始化参数
        Map<String,String> initParas = new HashMap<String,String>();
        initParas.put("loginUsername","admin");//它这个账户密码是固定的
        initParas.put("loginPassword","123456");
        //允许谁能防伪
        initParas.put("allow","");//这个值为空或没有就允许所有人访问,ip白名单
        //initParas.put("allow","localhost");//只允许本机访问,多个ip用逗号,隔开
        //initParas.put("deny","");//ip黑名单,拒绝谁访问 deny和allow同时存在优先deny
        // initParas.put("resetEnable","false");//禁用HTML页面的Reset按钮
        bean.setInitParameters(initParas);
        return bean;
    }
        //2、配置一个web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        Map<String,String> initParams = new HashMap<>();
        initParams.put("exclusions","*.js,*.css,/druid/*");

        bean.setInitParameters(initParams);

        bean.setUrlPatterns(Arrays.asList("/*"));

        return  bean;
    }
}

创建log4j.properties配置文件,实现一个简单的log4j

### set log levels ###
log4j.rootLogger = debug,stdout 

### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =  %5p   %n

这时候访问localHost:8080/druid就会跳转到后台监控,并输入密码。

10.整合mybatis

MyBatis-Spring-Boot-Starter类似一个中间件,链接Spring Boot和MyBatis,构建基于Spring Boot的MyBatis应用程序。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
  1. 创建User类
public class User {
   private  Integer id;
   private  String name;
   private  String pwd;
   //其他方法省略
}
  1. 创建mapper类
@Mapper  //将这个类注解成一个mapper 可以启动器上使用@MapperScan("地址")扫描器
@Repository   //类似于controller层和service层
public interface Usermapper {
    //查询全部数据
    List<User> getUSerlist();

    //增添数据
    int adduser(User user);

    //修改user
    int updateuser(Map<String,Object> map);

    //删除数据
    int removeuser(int id);
}
  1. 在resources目录下创建mapper的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.yc.springdemo.mapper.Usermapper">
    <select id="getUSerlist" resultType="user">
        select *
        from user.user;
    </select>
    
    
    <insert id="adduser" parameterType="user" >
        insert into user.user
        values (#{id},#{name},#{pwd});
    </insert>

    <update id="updateuser" parameterType="map" >
        update user .user
        set  name=#{uname} ,pwd=#{upwd}
        where id=#{uid};
    </update>
    
    
    <delete id="removeuser"  >
        delete
        from user.user
        where id=#{id};
    </delete>
</mapper>
  1. 在application.yaml中配置
spring:
  datasource:
    username: 用户名
    password: 密码
    url: jdbc:mysql://localHost:3306/user?useSSL=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml  #扫描mapper的xml
  type-aliases-package: com.yc.springdemo.pojo    #将扫描包下的内容起别名
  1. 在controller层实现增删改查
@Controller
@RestController
public class login {

    @Autowired
    private Usermapper usermapper;

    //查询全部数据
    @GetMapping("/getuser")
    public List<User>  userList(){
        List<User> uSerlist = usermapper.getUSerlist();
        return uSerlist;
    }
    //增添一条数据
    @GetMapping("/adduser/{id}")
    public int  adduser(@PathVariable Integer id){
        User user = new User();
        user.setId(id);
        user.setName("我的世界");
        user.setPwd("kkk");
        return usermapper.adduser(user);
    }

    //根据id修改一条数据
    @GetMapping("/update/{id}")
    public int  updateUser(@PathVariable  Integer id){
        HashMap<String, Object> map = new HashMap<>();
        map.put("uid",id);
        map.put("uname","我的世界");
        map.put("upwd","kkkk");
        return  usermapper.updateuser(map);
    }

    //删除一条数据
    @GetMapping("/remove/{id}")
    public int  removeuser(@PathVariable Integer  id){
        return usermapper.removeuser(id);
    }
}

11.springboot集成swagger

开发中有很多接口的开发,接口需要配合完整的接口文档才更方便沟通、使用,Swagger是一个用于自动生成在线接口文档的框架,并可在线测试接口,可以很好的跟Spring结合,只需要添加少量的代码和注解即可,而且在接口变动的同时,即可同步修改接口文档,不用再手动维护接口文档。Swagger3是17年推出的最新版本,相比于Swagger2配置更少,使用更方便

我这里使用的最新版本3.0.0的swagger,不在u需要导入swagger2和swagger-ui这两个包,因为在导入3.0.0的swagger启动包后,自动将一些包注入进去。

  1. 导入swagger的包
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
  1. yaml配置
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

**注意:**上面这个yaml配置是专门争对springboot和swagger3不兼容解决的,如果出现 Failed to start bean 'documentationPluginsBootstrapper';这个错误,那就是要配置yaml,这个问题的主要原因确实是SpringBoot版本过高导致。如果你用的是SpringBoot2.5.x及之前版本是没有问题的。Spring Boot 2.6.X使用PathPatternMatcher匹配路径,Swagger引用的Springfox使用的路径匹配是基于AntPathMatcher的。

1. 主方法添加@EnableOpenApi注解

@EnableOpenApi  //相当于开启swagger3的API,其实不开启也行
@SpringBootApplication
public class SpringdemoApplication   {
    public static void main(String[] args) {
        SpringApplication.run(SpringdemoApplication.class, args);
    }
}

2.swagger3config的配置

首先配置swagger的apiInfo

1, 方法一

//配置swagger的apiInfo
private  ApiInfo apiInfo(){
    return  new ApiInfo(
        "超哥的swagger",
        "这个作者很酷",
        "v1.0",
        "https://www.baidu.com",
        new Contact("超哥","https://www.baidu.com","3226872969@qq.com"),
        "apache 2.0",
        "https://www.baidu.com",
        new ArrayList());
}

上面是直接使用的构造方法,结构混乱

  1. 方法二
//配置swagger的apiInfo
private  ApiInfo apiInfo(){
    return  new ApiInfoBuilder()
        .title("超哥的网站")
        .description("程序员的设计开发")
        .contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
        .version("v1.0")
        .build();

}

方法er显得就很简单,其实在swagger中,主要的也就是方法二中的那几个参数,多的也无济于事

@Configuration
public class swagger3config {

    @Bean   //配置swagger的coket的bean实例
    public Docket docket(){
        //swagger3使用DocumentationType.OAS_30,swagger2使用DocumentationType SWAGGER_2
        return  new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .enable(true) //开启swagger,默认为true,禁用就不能访问swagger
            .select()
            /**
                 * apis方法是select()下的方法,里面可以有以下方式
                 * 1. RequestHandlerSelectors  用于扫描接口的方式
                 * 2. .any()扫描全部
                 * 3.  .none()不扫描
                 * 4. .withClassAnnotation()扫描一个类上的注解,只有存在这个注解才会被扫描
                 * 5. 。withMethodAnnotation()扫描方法上的注解,只有这个方法上的注解存在才会被扫描
                 * 6. .basePackage()扫描指定的包
                 */
            .apis(RequestHandlerSelectors.basePackage("com.yc.springdemo.controller"))
            /**  过滤接口
                 *  1. PathSelectors扫描指定的接口
                 *  2. .ant("/yc/"")过滤yc下的所有指定路径
                 *  3. .any()所有的路径
                 *  4. 。none()不过滤任何路径
                 *  5. 。regex()通过正则表达式过滤接口
                 */
            .paths(PathSelectors.any())
            .build();
    }
    //配置swagger的apiInfo
    private  ApiInfo apiInfo(){
        return  new ApiInfoBuilder()
            .title("超哥的网站")
            .description("程序员的设计开发")
            .contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
            .version("v1.0")
            .build();

    }
}

3.不同环境下,swagger的权限

其实也就是,在生产环境下不能访问swagger,测试环境下可以访问swagger

  1. 使用yaml配置生产环境和测试环境
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  profiles:
    active: test
---
#开发环境
spring:
  config:
    activate:
      on-profile: dev
swagger:
  enble: false
---
#测试环境
spring:
  config:
    activate:
      on-profile: test
swagger:
  enble: true
  1. 在swagger3config配置
@Configuration
public class swagger3config {
    @Value("${swagger.enble}")
    private boolean  enble;

    @Bean   //配置swagger的coket的bean实例
    public Docket docket(){
        //swagger3使用DocumentationType.OAS_30,swagger2使用DocumentationType SWAGGER_2
        return  new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .enable(enble)
            .select()
            /**
                 * apis方法是select()下的方法,里面可以有以下方式
                 * 1. RequestHandlerSelectors  用于扫描接口的方式
                 * 2. .any()扫描全部
                 * 3.  .none()不扫描
                 * 4. .withClassAnnotation()扫描一个类上的注解,只有存在这个注解才会被扫描
                 * 5. 。withMethodAnnotation()扫描方法上的注解,只有这个方法上的注解存在才会被扫描
                 * 6. .basePackage()扫描指定的包
                 */
            .apis(RequestHandlerSelectors.basePackage("com.yc.springdemo.controller"))
            /**  过滤接口
                 *  1. PathSelectors扫描指定的接口
                 *  2. .ant("/yc/"")过滤yc下的所有指定路径
                 *  3. .any()所有的路径
                 *  4. 。none()不过滤任何路径
                 *  5. 。regex()通过正则表达式过滤接口
                 */
            .paths(PathSelectors.any())
            .build();
    }
    //配置swagger的apiInfo
    private  ApiInfo apiInfo(){
        return  new ApiInfoBuilder()
            .title("超哥的网站")
            .description("程序员的设计开发")
            .contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
            .version("v1.0")
            .build();

    }
}

4.swagger3注解的详解

@Api  用在controller层的请求类上,表示对类的说明
      tags="说明该类的作用,可以在UI界面上看到的注解"
      value="该参数没什么意义,在UI界面上也看不到,所以不需要配置"
          
@ApiOperation  用在请求的方法上,说明方法的用途、作用 
               value="说明方法的用途、作用"
                notes="方法的备注说明"

@ApiParam    描述参数
             value="参数说明"
             required="boolean 是否必须"

@ApiImplicitParams:用在请求的方法上,表示一组参数说明
    @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
        name:参数名
        value:参数的汉字说明、解释
        required:参数是否必须传
        paramType:参数放在哪个地方
            · header --> 请求参数的获取:@RequestHeader
            · query --> 请求参数的获取:@RequestParam
            · path(用于restful接口)--> 请求参数的获取:@PathVariable
            · body(不常用)
            · form(不常用)    
        dataType:参数类型,默认String,其它值dataType="Integer"       
        defaultValue:参数的默认值

@ApiResponses:用在请求的方法上,表示一组响应
    @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
        code:数字,例如400
        message:信息,例如"请求参数没填好"
        response:抛出异常的类

@ApiModel:用于bean上,描述返回对象
    @ApiModelProperty:用在属性上,描述类的属性

12.springboot自带的异步任务

  • 同步是阻塞模式,异步是非阻塞模式。
  • 同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会—直等待下去,知道收到返回信息才继续执行下去
  • 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回式系统会通知进程进行处理,这样可以提高执行的效率。

异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

springboot提供一下两个注解

  • @Async 用在方法上,表示异步任务

  • @EnableAsync 用在主方法上,表示开启异步支持

  1. 创建service层
@Async
public void hello(){
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("hello");
}

SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;

  1. 创建controller层
@Controller
@RestController
public class login {

    @Autowired
    private userService   userService;

    @RequestMapping("/login")
    public String  hello(){
        userService.hello();
        return "hello" ;
    }
}

13.邮件任务

邮件任务简单地说就是一个人向另一个人发送邮件,

导入pom坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

1. 获取第三方的授权码

打开qq邮箱—>设置—>账户—>开启POP3/SMTP服务

2.配置邮件信息

  1. application.properties
# qq邮箱
spring.mail.username=qq号@qq.com
# 刚刚生成的授权码
spring.mail.password=授权码
# qq邮箱的host
spring.mail.host=smtp.qq.com
#开启加密验证(qq邮箱)
spring.mail.properties.mail.smtp.ssl.enable=true

或者yaml

spring:
  mail:
    username: qq号@qq.com
    password: 授权码
    host: smtp.qq.com
    properties:
      mail:
        smtp:
          ssl:
            enable: true

3.简单邮件发送

调用JavaMailSenderImpl类,使用SimpleMailMessage发送邮件内容

@SpringBootTest
class SpringdemoApplicationTests {
    @Autowired
    JavaMailSenderImpl mailSender;
    @Test
    void contextLoads() throws SQLException {
        SimpleMailMessage message = new SimpleMailMessage();
        //邮件标题
        message.setSubject("发给你的");
        //邮件内容
        message.setText("你好");
        //邮件接收方
        message.setTo("接收方qq号@qq.com");
        //邮件发送方
        message.setFrom("发送方qq号@qq.com");
        //发送邮件
        mailSender.send(message);
    }
}

4.复杂邮件发送

@SpringBootTest
class SpringdemoApplicationTests {
    @Autowired
    JavaMailSenderImpl mailSender;
    @Test
    void contextLoads() throws SQLException, MessagingException {
        //复杂邮件创建
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组合创建复杂邮件内容
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        //邮件标题
        mimeMessageHelper.setSubject("复杂邮件");
        //邮件内容,true代表支持html格式
        mimeMessageHelper.setText("<h1>这是一个美女图片</h1>",true);
        //发送图片
        mimeMessageHelper.addAttachment("美女.png",new File("文件地址"));
        //接收方
        mimeMessageHelper.setTo("qq号@qq.com");
        //发送方
        mimeMessageHelper.setFrom("qq号@qq.com");
        //发送
        mailSender.send(mimeMessage);
    }
}

将上述用到的内容封装成一个方法

public void SendMail(Boolean html, String title, String text, File file, String sendTo, String sendFrom) throws MessagingException {
    //复杂邮件
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    //组装
    MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);

    mimeMessageHelper.setSubject(title);
    mimeMessageHelper.setText(text, html);//true,开启html解析
    mimeMessageHelper.addAttachment("1.jpg", file);

    mimeMessageHelper.setTo(sendTo);
    mimeMessageHelper.setFrom(sendFrom);
    mailSender.send(mimeMessage);
}

14.集成restTemplate

restTemplate底层是基于HttpURLConnection实现的restful风格的接口调用,类似于webservice,rpc远程调用,但其工作模式更加轻量级,方便于rest请求之间的调用,完成数据之间的交互,在springCloud之中也有一席之地。

1.restTemplate常用方法列表

forObeject跟forEntity有什么区别呢?主要的区别是forEntity的功能更加强大一些,其返回值是一个ResponseEntity,更加方便我们获得响应的body,head等信息。exchange方法和其他方法不同之处就是能自己定义的rest请求方式。

  1. get请求方法预览
方法返回值
getForObject(String,Class ,Object … )T
getForObject(String,Class,Map<String,?>)T
getForObject(String,Class)T
getForEntity(String,Class,Object…)ResponseEntity
getForEntity(String,Class,Map<String,?>)ResponseEntity
getForEntity(String,Class)ResponseEntity

ResponseEntity方法详解

  • getBody()获取ResponseEntity的泛型实体类,从中获取实体类的各种方法
  • getHeaders()获取ResponseEntity的头,例如跨域设置,返回类型,返回时间
  • getStatusCodeValue()返回ResponseEntity的状态,例如200,404等
  1. post请求方法实现
方法返回值
postForObject(String,Object,Class)T
postForObject(String,Object,Class,Object…)T
postForObject(String,Object,Class,Map<String,?>)T
postForLocation(String,Object,Object…)URI
postForLocation(String,Object,Map<String,?>)URI
postForLocation(String,Object)URI
postForEntity(String,Object,Class)ResponseEntity
postForEntity(String,Object,Class,Object…)ResponseEntity
postForEntity(String,Object,Class,Map<String,?>)ResponseEntity
  1. put请求方式实现
方法返回值
put(String,Object)void
put(String,Object,Object…)void
put(String,Object,Map)void
  1. delete请求方法与put方法相似,但没有request参数
  2. exchange自定义请求方法

暂时不做过多的解释,使用get和post就已经足够

2.rest接口调用实示例

restTemplate配置

restTemplate简单配置如下:

@Configuration
public class RestTemplateConfig {

  // 配置 RestTemplate
  @Bean
  public RestTemplate restTemplate(ClientHttpRequestFactory factory){
    return new RestTemplate(factory);
  }

  @Bean
  public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
    // 创建一个 httpCilent 简单工厂
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    // 设置连接超时
    factory.setConnectTimeout(15000);
    // 设置读取超时
    factory.setReadTimeout(5000);
    return factory;
  }
}

4.示例

  1. get请求接口调用示例
  @GetMapping("user")
  public String getUser(){
    return "youku1327";
  }

  @GetMapping("user/{name}")
  public String getUserName(@PathVariable String name){
    return name;
  }

GET参数说明

  • 第一个参数是url。
  • 第二个参数是返回值类型。
  • 第三个参数是uri地址路径变量。
@Test
public void testGETNoParams(){
    String result = restTemplate.getForObject("http://localhost:8090/youku1327/user", String.class);
    System.out.println(result);
}
@Test
public void testGETParams(){
    // http://localhost:8090/youku1327/user/{1}
    String result = restTemplate.getForObject("http://localhost:8090/youku1327/user/{name}", String.class,"lsc");
    System.out.println(result);
}
  1. post请求接口调用示例

POST请求参数说明

  • 第一个参数是url。
  • 第二个参数是请求参数。
  • 第三个参数是返回值类型。
  • 第三个参数是uri地址路径变量。
@PostMapping("provider")
public ResponseEntity<String> addData(@RequestBody JSONObject jsonObject){
    String user = (String) jsonObject.get("user");
    return ResponseEntity.ok(user);
}
@Test
public void testPostMethod() throws MalformedURLException {
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("user","youku1327");
    HttpHeaders httpHeaders = new HttpHeaders();
    // 设置请求类型
    httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
    // 封装参数和头信息
    HttpEntity<JSONObject> httpEntity = new HttpEntity(jsonObject,httpHeaders);
    String url = "http://localhost:8090/youku1327/provider";
    ResponseEntity<String> mapResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
    System.out.println(mapResponseEntity.getBody());
}

其他请求的类型访问https://www.jb51.net/article/185208.htm

15定时任务

  1. TaskSchedulerr 任务调度者接口
  2. TaskExecutor 任务调度者j接口
  3. @EnableScheduling 开启定时任务的注解(在启动方法上使用)
  4. @Scheduled 什么时候执行

16.整合redis数据库

spring boot操作数据:spring-data jpa jdbc mongodb redis

springdata也是spring boot齐名的项目

我们之所以要学习Redis,是要令我们Java程序更加有效率,我们在使用数据库的时候给它加上一个缓存中间件,就是用来提高我们程序的效率的,那么当然,Redis还是要集成到我们SpringBoot项目里面的!!

首先导入redis的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

到我们SpringBoot2.x版本,其内置的Redis中间件再也不是Jedis了,而是换成了lettuce。我们点进redis依赖就可以发现

在spring-boot-starter-data-redis中使用的是下面的lettuce类型

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.8.RELEASE</version>
    <scope>compile</scope>
</dependency>
  • lettuce: Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。

  • Jedis: Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加 物理连接。 这种方式更加类似于我们 BIO 一条线程连一个客户端,并且是阻塞式的,会一直连接着客户端等待客户端的命令

RedisAutoConfiguration 分析

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)  //配置文件属性
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
 
 // redis的模板实例,可以帮助我们快速操作redis
	@Bean
 // 如果这个名为redisTemplate实例不存在的话,该bean生效,那么我们可以代替SpringBoot,自定义我们的redis模板实例!!!
	@ConditionalOnMissingBean(name = "redisTemplate")
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) 	  {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}
 
 // 因为我们很多情况都是操作String类型的数据,那么这个类帮我们也配置了String的redis模板供我们使用!
 // 其方法与上面的redisTemplate一样
	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
 {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}
}

RedisProperties配置文件属性

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
 
	/**
	 * 可以配置使用的db下标
	 */
	private int database = 0;
 
	/**
	 * 这个配置可以让我们连接到远程的redis中。例如:
	 * redis://user:password@example.com:6379
	 */
	private String url;
 
	/**
	 * Redis服务端的主机名
	 */
	private String host = "localhost";
 
	/**
	 * Login username of the redis server.
	 */
	private String username;
 
	/**
	 * Login password of the redis server.
	 */
	private String password;
 
	/**
	 * Redis的端口号
	 */
	private int port = 6379;
 
	/**
	 * 是否开启安全认证
	 */
	private boolean ssl;
 
	/**
	 * Read timeout.
	 */
	private Duration timeout;
 
	/**
	 * Connection timeout.
	 */
	private Duration connectTimeout;
 
	/**
	 * Client name to be set on connections with CLIENT SETNAME.
	 */
	private String clientName;
 
	/**
	 * Type of client to use. By default, auto-detected according to the classpath.
	 */
	private ClientType clientType;
 
	private Sentinel sentinel;
 
	private Cluster cluster;
}

其中主机名和端口号都有默认值,如果我们连自己的电脑,那么这两个配置都可以不用修改!

如果要使用其他远程的数据库,则要使用以下配置

spring:
  redis:
    port: 6379  #端口号
#   database: 0
    password: redis的密码
    host: ip地址

1.测试连接

@SpringBootTest
class SpringdemoApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void contextLoads(){

        //获取redis数据库连接对象
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        //关闭这个连接
        connection.close();

        //添加key为String的value
        redisTemplate.opsForValue().set("name","nihao");
        //获取这个value
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

}

**注意:**opsForXXXXX()这个方法是开启某种类型的方法,比如set,hash,list等

2.自定义RedisTemplate

那么现在我们能在SpringBoot项目中使用Redis了,但是我们如果存中文的话会发生乱码!

我们在分析Redis配置文件中也发现了我们的RedisTemplate时可以让我们配置的,那么默认的RedisTemplate给我们配置了什么?

public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
    .......
        @SuppressWarnings("rawtypes")
        private @Nullable RedisSerializer keySerializer = null;
    @SuppressWarnings("rawtypes") 
    private @Nullable RedisSerializer valueSerializer = null;
    @SuppressWarnings("rawtypes")
    private @Nullable RedisSerializer hashKeySerializer = null;
    @SuppressWarnings("rawtypes")
    private @Nullable RedisSerializer hashValueSerializer = null;
    public void afterPropertiesSet() {

        super.afterPropertiesSet();

        boolean defaultUsed = false;

        if (defaultSerializer == null) {
            // 默认的序列化方式为JDK中自带的序列化
            defaultSerializer = new JdkSerializationRedisSerializer(
                classLoader != null ? classLoader : this.getClass().getClassLoader());
        }

        if (enableDefaultSerializer) {
            // 因为我们并没有设置其序列化的实例,那么在创建这个RedisTemplate实例的时候,全部都默认成了JDK的序列化方式!
            if (keySerializer == null) {
                keySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (valueSerializer == null) {
                valueSerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashKeySerializer == null) {
                hashKeySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashValueSerializer == null) {
                hashValueSerializer = defaultSerializer;
                defaultUsed = true;
            }
        }

        if (enableDefaultSerializer && defaultUsed) {
            Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
        }

        if (scriptExecutor == null) {
            this.scriptExecutor = new DefaultScriptExecutor<>(this);
        }

        initialized = true;
    }
}

我们可以知道,如果我们不配置RedisTemplate的序列化方式,默认的是使用JDK中的序列化方式,那么我们点进这个JdkSerializationRedisSerializer类中看看

点进深层我们发现,里面其实创建了一个序列化转换器,而转换器默认的构造方法是JDK默认的序列化工具,其实现了Serializer接口

void serialize(T object, OutputStream outputStream) throws IOException;

因为我们java使用ISO-8859-1编码进行传输数据的,那么我们传输字符串的话,那么编解码会不一致,一定会出现乱码!!!

我们从上面分析可以知道,当我们的类中出现以redisTemplate命名的bean的时候,SpringBoot的配置将不会生效!

  1. 只修改字符编码错误,不能被序列化
@Bean
public RedisTemplate<String, Object> stringSerializerRedisTemplate() {
    RedisSerializer<String> stringSerializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(stringSerializer);
    redisTemplate.setValueSerializer(stringSerializer);
    redisTemplate.setHashKeySerializer(stringSerializer);
    redisTemplate.setHashValueSerializer(stringSerializer);
    return redisTemplate;
}

这样修改也可以是字符编码不出现乱码,但这不是唯一的解决办法。

  1. 接管一下这个redisTemplate类, 配置自己的序列化
// 标志为配置类
@Configuration
public class myConfig {

    // 把这个bean的name设置为redisTemplate,这样我们才能全面接管redisTemplate!
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)   {
        //一般为了自己方便,我们直接使用String,Object
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        // jackson序列化所有的类
        Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new  Jackson2JsonRedisSerializer(Object.class);
        // jackson序列化的一些配置
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        Jackson2JsonRedisSerializer.setObjectMapper(om);

        // String的序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        //将我们的key采用String的序列化方式
        template.setKeySerializer(stringSerializer);
        //将我们的hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringSerializer);
        //value采用jackson序列化方式
        template.setValueSerializer(Jackson2JsonRedisSerializer);
        //hash的value也采用jackson序列化方式
        template.setHashValueSerializer(Jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

3.封装自己的redisutil类

我们在常常使用redis的时候,需要反复的使用同一个和方法,或者存储一个数据可能需要好几步,这时候哦我们呢就会封装一个库

@Component
public final class Redisutil {

    @Autowired
    @Qualifier("redisTemplate")
    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) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return 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);
    }


    // ================================Map=================================

    /**
     * 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);
    }


    // ============================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 {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================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 {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }

}

网址:https://www.jianshu.com/p/2909ee88cabb

17.整合fastjson(响应)

springboot默认使用jackson作为json解析框架,如果使用fastjson,可以按照下列方式配置使用

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>${fastjson.version}</version>
</dependency>

注: fastjson爆出远程代码执行高危漏洞,影响范围FastJSON 1.2.30及以下版本,FastJSON 1.2.41至1.2.45版本;官方建议升级至FastJSON最新版本,建议升级至1.2.58版本。

1.在配置类中配置

一.继承方式

继承WebMvcConfigurerAdapter类,重写configureMessageConverters方法;在springboot2.x版本中WebMvcConfigurerAdapter类已经过时了

@Configuration
@EnableWebMvc
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
    /**
     * 配置FastJson为方式一
     * @return*/
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        /*
         * 1、需要先定义一个convert转换消息的对象 2、添加fastJson的配置信息,比如:是否要格式化返回json数据 3、在convert中添加配置信息
         * 4、将convert添加到converters当中
         *
         */
        // 1、需要先定义一个·convert转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2、添加fastjson的配置信息,比如 是否要格式化返回json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        // 3、在convert中添加配置信息.
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4、将convert添加到converters当中.
        converters.add(fastConverter);
    }

}

二.通过@Bean方式注入

注入Bean : HttpMessageConverters

@Configuration
public class HttpConverterConfig {
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1.定义一个converters转换消息的对象
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);

        //解决中文乱码问题:在方法内部添加这段代码
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON);
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        // 3.在converter中添加配置信息
        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4.将converter赋值给HttpMessageConverter
        HttpMessageConverter<?> converter = fastConverter;
        // 5.返回HttpMessageConverters对象
        return new HttpMessageConverters(converter);
    }
}

2.fastjson中SerializerFeature的用法及中文注解

package com.alibaba.fastjson.serializer;
/**
 * @author wenshao<szujobs@hotmail.com>
 */
public enum SerializerFeature {
    QuoteFieldNames,//输出key时是否使用双引号,默认为true 
    /**
     * 
     */
    UseSingleQuotes,//使用单引号而不是双引号,默认为false
    /**
     * 
     */
    WriteMapNullValue,//是否输出值为null的字段,默认为false 
    /**
     * 
     */
    WriteEnumUsingToString,//Enum输出name()或者original,默认为false
    /**
     * 
     */
    UseISO8601DateFormat,//Date使用ISO8601格式输出,默认为false
    /**
     * @since 1.1
     */
    WriteNullListAsEmpty,//List字段如果为null,输出为[],而非null 
    /**
     * @since 1.1
     */
    WriteNullStringAsEmpty,//字符类型字段如果为null,输出为"",而非null 
    /**
     * @since 1.1
     */
    WriteNullNumberAsZero,//数值字段如果为null,输出为0,而非null 
    /**
     * @since 1.1
     */
    WriteNullBooleanAsFalse,//Boolean字段如果为null,输出为false,而非null
    /**
     * @since 1.1
     */
    SkipTransientField,//如果是true,类中的Get方法对应的Field是transient,序列化时将会被忽略。默认为true
    /**
     * @since 1.1
     */
    SortField,//按字段名称排序后输出。默认为false
    /**
     * @since 1.1.1
     */
    @Deprecated
    WriteTabAsSpecial,//把\t做转义输出,默认为false
    /**
     * @since 1.1.2
     */
    PrettyFormat,//结果是否格式化,默认为false
    /**
     * @since 1.1.2
     */
    WriteClassName,//序列化时写入类型信息,默认为false。反序列化是需用到

    /**
     * @since 1.1.6
     */
    DisableCircularReferenceDetect,//消除对同一对象循环引用的问题,默认为false

    /**
     * @since 1.1.9
     */
    WriteSlashAsSpecial,//对斜杠'/'进行转义

    /**
     * @since 1.1.10
     */
    BrowserCompatible,//将中文都会序列化为\uXXXX格式,字节数会多一些,但是能兼容IE 6,默认为false

    /**
     * @since 1.1.14
     */
    WriteDateUseDateFormat,//全局修改日期格式,默认为false。JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd";JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);

    /**
     * @since 1.1.15
     */
    NotWriteRootClassName,//暂不知,求告知

    /**
     * @since 1.1.19
     */
    DisableCheckSpecialChar,//一个对象的字符串属性中如果有特殊字符如双引号,将会在转成json时带有反斜杠转移符。如果不需要转义,可以使用这个属性。默认为false 

    /**
     * @since 1.1.35
     */
    BeanToArray //暂不知,求告知
        ;

    private SerializerFeature(){
        mask = (1 << ordinal());
    }

    private final int mask;

    public final int getMask() {
        return mask;
    }

    public static boolean isEnabled(int features, SerializerFeature feature) {
        return (features & feature.getMask()) != 0;
    }

    public static int config(int features, SerializerFeature feature, boolean state) {
        if (state) {
            features |= feature.getMask();
        } else {
            features &= ~feature.getMask();
        }

        return features;
    }
}

3.Fastjson反序列化java对象

后面在常使用json时,我们可能要把Json转化为java对象,这时候就要使用下面的方法

//这个是从redis中获取的json字符串
Object name = redisutil.get("name");
//先转成json对象
String jsonObject = JSON.toJSONString(name);
//转换成java对象
User user = JSON.parseObject(jsonObject,User.class);

18.整合mybatis-plus

MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

  1. 创建数据库
  2. 创建一个表,写入数据
CREATE TABLE user
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
	PRIMARY KEY (id)
);
-- 在真实开发中:会存在 version(乐观锁)、delete(逻辑删除)、gmt_create(创建时间)
--			         gmt_modify(修改时间)

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
  1. 搭建springboot环境
  2. 导入pom坐标
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时带入mybatis跟mybatis-plus,可能会产生冲突

  1. 连接数据库,这一步与mybatis方式相同

url字段中 设置时区:serverTime=GMT%2b8 (%2b 就是+的意思,这里是指加8个小时,以北京东八区为准)

spring:
  datasource:
    username: 用户名
    password: 密码
    url: jdbc:mysql://ip地址:3306/mybatis?userUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
  1. 创建实体类,借助lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
  1. 创就mapper接口,并且继承BaseMapper接口
@Mapper
public interface  UserMapper extends BaseMapper<User> {
}
  1. 创建测试类
@SpringBootTest
class MybatisplusApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        List<User> userList = userMapper.selectList(null);
        for (User user : userList) {
            System.out.println(user);
        }
    }
}
  1. mybatis-plus的基本配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 默认日志配置
  global-config:
    banner: false  #取消mybatis-plus的banner
  mapper-locations: classpath:mybatis/mapper/*.xml   #扫描mapper.xml
  type-aliases-package: com.yc.mybatisplus.pojo   #别名

1.主键生成策略

分布式系统唯一ID生成方案:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法:

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!

主键自增

我们需要配置主键自增:

1、实体类字段上增加 @TableId(type = IdType.AUTO)

2、数据库字段一定要是设置自增的!

源码解释

public enum IdType {
    AUTO(0),  // 数据库id自增
    NONE(1),  // 未设置主键
    INPUT(2), // 手动输入
    ID_WORKER(3), // 默认的全局id
    UUID(4),  // 全局唯一id
    ID_WORKER_STR(5); // ID_WORKER 字符串表示法
}

改为手动输入之后,就需要自己配置id

public class User {
    // 对应数据库的主键(uuid、自增id、雪花算法、redis、zookeeper)
    @TableId(type = IdType.INPUT)  // 默认方案
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

2.插入操作

// 测试插入
@Test
public void testInsert(){
    User user = new User();
    user.setName("小爽帅到拖网速");
    user.setAge(20);
    user.setEmail("1372713212@qq.com");
    int result = userMapper.insert(user);  // 帮我们自动生成id
    System.out.println(result); // 受影响的行数
    System.out.println(user); // 发现,id自动回填
}

3.更新操作

@Test
void  update() {
    User user = new User();
    user.setId(6l);
    user.setAge(30);
    user.setName("超爷");
    user.setEmail("3222@QQ.COM");
    int update = userMapper.updateById(user);
    System.out.println(update);
}

4.自动填充

创建时间、修改时间!这些个操作一般都是自动化完成的,我们不希望手动更新!

阿里巴巴开发手册:所有的数据库表:gmt_create 、gmt_modify几乎所有的表都要配置上,而且需要自动化!

方式一:数据库级别的修改 (工作中是不允许你修改数据库)

  1. 在表中新增字段create_time、update_time
  2. 再次测试插入方法,我们需要先把实体类同步!
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private Date create_time;
    private Date update_time;
}

方式二,代码级别

  1. 删除数据库的默认值,更新操作,也就是根据当前时间戳更新关闭
  2. 实体类字段属性上需要增加注解
// 字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date create_time;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date update_time;
  1. 编写处理器来处理这个注解即可!

由于这个处理器在Springboot下面, mybatis会自动处理我们写的所有的处理器

当我们执行插入操作的时候,自动帮我们通过反射去读取哪边有对应注解的字段,从而把处理器代码插入成功,会自动帮我把createTime,updateTime插入值

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时的更新策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start  insert  fill.....");
        this.setFieldValByName("create_time",new Date(),metaObject);
        this.setFieldValByName("update_time",new Date(),metaObject);
    }

    //插入方式的更新策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start  update  fill.....");
        this.setFieldValByName("update_time",new Date(),metaObject);
    }
}

注意:处理器指定字段中大写字母不能被识别成-小写字母的格式

5.乐观锁

在面试过程中,我们经常会被问道乐观锁,悲观锁,其实原理非常简单

乐观锁 OptimisticLockerInnerInterceptor

顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁!如果出现问题就再次更新测试

这里引出 旧version 新version

乐观锁:当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败‘
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "xiaoshaung",version = version + 1
where id = 2 and version = 1

-- B 线程抢先完成,这个时候 version = 2 ,会导致 A 修改失败!
update user set name = "小爽",version = version + 1
where id = 2 and version = 1
  1. 给数据库中增加version字段

  2. 实体类加对应的字段

//乐观锁version注解 
@Version  
private Integer version;
  1. 注册组件
@EnableTransactionManagement
@Configuration
public class mybatisplusconfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
  1. 单线程下测试
@Test
void  update() {
    User user = userMapper.selectById(8l);
    user.setName("你好超哥");
    user.setEmail("366666@qqq.com");
    int i = userMapper.updateById(user);
    System.out.println(i);
}
  1. 多线程测试
// 测试乐观锁多线程失败  多线程
 /*
 线程1 虽然执行了赋值语句,但是还没进行更新操作,线程2就插队了抢先更新了,
 由于并发下,可能导致线程1执行不成功
 如果没有乐观锁就会覆盖线程2的值
 */
@Test
public void testOptimisticLock2(){
    // 线程1
    User user = userMapper.selectById(1);
    user.setName("xiaoshaung111");
    user.setEmail("123123132@qq.com");

    // 模拟另外一个线程执行了插队操作
    // 线程2
    User user2 = userMapper.selectById(1);
    user2.setName("xiaoshaung222");
    user2.setEmail("123123132@qq.com");
    userMapper.updateById(user2);

    // 自旋锁来多次尝试提交
    userMapper.updateById(user);
}

6.查询操作

  1. 单个查询
@Test
void  select() {
    User user = userMapper.selectById(1l);
    System.out.println(user);
}
  1. 根据多个id查询
@Test
void  select() {
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1,2,3));
    for (User user : users) {
        System.out.println(user);
    }
}
  1. 条件查询map
@Test
void  select() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","超哥");
    map.put("age","30");
    List<User> users = userMapper.selectByMap(map);
    for (User user : users) {
        System.out.println(user);
    }
}

7.分页查询

分页在网站使用的十分之多!

  1. 原始的limit 进行分页
  2. pageHepler 第三方插件
  3. Mybatis-Plus其实也内置了分页插件!

mybatisplus实现分页

  1. 拦截器组件即可
@EnableTransactionManagement
@Configuration
public class mybatisplusconfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }
}
  1. 直接使用page对象即可
// 测试分页查询
@Test
public void testPage(){
    // 参数1 当前页 ;参数2 页面大小
    Page<User> page = new Page<>(1,5);
    userMapper.selectPage(page,null);

    page.getRecords().forEach(System.out::println);
    System.out.println("getCurrent()"+page.getCurrent());
    System.out.println("page.getSize()"+page.getSize());
    System.out.println("page.getTotal()"+page.getTotal());
}

8.删除操作

  1. 根据id删除记录
@Test
void  select() {
    int i = userMapper.deleteById(1l);
    System.out.println(i);
}
  1. 批量删除
@Test
void  select() {
    int i = userMapper.deleteBatchIds(Arrays.asList(2,3,4));
    System.out.println(i);
}
  1. 通过Map定制删除
@Test
void  select() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","超哥");
    int i = userMapper.deleteByMap(map);
    System.out.println(i);
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:36:15  更:2022-07-03 10:37:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 22:54:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码