• newspringboot


    spring boot原理

    自动配置:

    pom.xml

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

    启动器:

    • <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
      </dependency>
      
      • 1
      • 2
      • 3
      • 4
    • 上面也就是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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    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}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.yaml常用写法

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

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

    1.yaml字面量写法

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

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

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

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

    输出:

    '你好呀\n你好'

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

    输出

    name='你好呀 你好'

    2.yaml对象写法

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

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

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

    3.YAML 数组写法

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

    pets:
      -dog
      -cat
      -pig
    
    • 1
    • 2
    • 3
    • 4

    行内写法:

    pets: [dog,cat,pig]
    
    • 1

    4.复合结构

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

    person:
      name: zhangsan
      age: 30
      pets:
        -dog
        -cat
        -pig
      car:
        name: QQ
      child:
        name: zhangxiaosan
        age: 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 在一个java实体类中写入
    /**
    * 将配置文件中配置的每一个属性的值,映射到这个组件中
    *
    * @ConfigurationProperties:告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定;
    * prefix = "parse":配置文件中哪个下面的所有属性进行一一映射
    *
    * 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能;
    */
    @Component
    @ConfigurationProperties(prefix = "parse")
    public class User {
        private  String name;
        private  Integer age;
        private List<String>  list;
        //其他方法省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意:

    • 只有在容器中的组件,才会拥有 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;
        //省略其他方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:

    • 如果是上面集合形式的,不能使用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=玩耍,电脑,跑步
    
    • 1
    • 2
    • 3

    创建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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.JSR303校验

    导入springboot依赖

    <!--JSR303校验-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意: 如果要使用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=)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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
    
    • 1

    其中,{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
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    # 开发环境
    server.port=8081
    
    • 1
    • 2

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

    # 测试环境
    server.port=8082
    
    • 1
    • 2

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

    # 生产环境
    server.port=8083
    
    • 1
    • 2

    即使主配置文件中设置了端口号,但是在主配置文件中设置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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

    # 默认配置
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    6.springboot Web开发

    要解决的问题

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

    1.静态资源导入问题

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

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

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

    spring:
      mvc:
        static-path-pattern: #自己的资源目录
    
    • 1
    • 2
    • 3

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

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    声明空间

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

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

    在 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.注册拦截器

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

    @Configuration
    public class loginconfig implements WebMvcConfigurer {
        
        @Bean
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoginInterceptor());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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/**"); //放行登录页,登陆操作,静态资源
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    • 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 创建主界面main
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <body>
            <h1>主界面</h1>
        </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 创建一个配置类注入myconfig类
    @Configuration
    public class loginconfig {
    
        @Bean
        public  myconfig myconfig(){
            return  new myconfig();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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";
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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>
    
    • 1
    • 2
    • 3
    • 4

    2.导入数据库驱动

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

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    4.测试

    @SpringBootTest
    class SpringdemoApplicationTests {
    
        @Autowired
        private  DataSource dataSource;
    
        @Test
        void contextLoads() throws SQLException {
            //创建连接
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //关闭连接
            connection.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如果控制台输出 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    创建测试类

    @SpringBootTest
    class SpringdemoApplicationTests {
        @Autowired
        private  DataSource dataSource;
    
        @Test
        void contextLoads() throws SQLException {
            System.out.println(dataSource.getClass());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果控制台显示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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    上面除了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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    创建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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这时候访问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
    • 2
    • 3
    • 4
    • 5
    1. 创建User类
    public class User {
       private  Integer id;
       private  String name;
       private  String pwd;
       //其他方法省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    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
    • 2
    • 3
    • 4
    • 5
    1. yaml配置
    spring:
      mvc:
        pathmatch:
          matching-strategy: ant_path_matcher
    
    • 1
    • 2
    • 3
    • 4

    **注意:**上面这个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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    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();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方法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();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    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();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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:用在属性上,描述类的属性
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    12.springboot自带的异步任务

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

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

    springboot提供一下两个注解

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

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

    1. 创建service层
    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    1. 创建controller层
    @Controller
    @RestController
    public class login {
    
        @Autowired
        private userService   userService;
    
        @RequestMapping("/login")
        public String  hello(){
            userService.hello();
            return "hello" ;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    13.邮件任务

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

    导入pom坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    或者yaml

    spring:
      mail:
        username: qq号@qq.com
        password: 授权码
        host: smtp.qq.com
        properties:
          mail:
            smtp:
              ssl:
                enable: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.示例

    1. get请求接口调用示例
      @GetMapping("user")
      public String getUser(){
        return "youku1327";
      }
    
      @GetMapping("user/{name}")
      public String getUserName(@PathVariable String name){
        return name;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. post请求接口调用示例

    POST请求参数说明

    • 第一个参数是url。
    • 第二个参数是请求参数。
    • 第三个参数是返回值类型。
    • 第三个参数是uri地址路径变量。
    @PostMapping("provider")
    public ResponseEntity<String> addData(@RequestBody JSONObject jsonObject){
        String user = (String) jsonObject.get("user");
        return ResponseEntity.ok(user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    其他请求的类型访问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>
    
    • 1
    • 2
    • 3
    • 4

    到我们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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

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

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

    spring:
      redis:
        port: 6379  #端口号
    #   database: 0
        password: redis的密码
        host: ip地址
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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"));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    **注意:**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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

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

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

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

    因为我们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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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;
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561

    网址: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>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注: 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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    二.通过@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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 搭建springboot环境
    2. 导入pom坐标
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.2</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    说明:我们使用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
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 创建实体类,借助lombok
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    public class User {
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 创就mapper接口,并且继承BaseMapper接口
    @Mapper
    public interface  UserMapper extends BaseMapper<User> {
    }
    
    • 1
    • 2
    • 3
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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 字符串表示法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

    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自动回填
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    方式二,代码级别

    1. 删除数据库的默认值,更新操作,也就是根据当前时间戳更新关闭
    2. 实体类字段属性上需要增加注解
    // 字段添加填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date create_time;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date update_time;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 给数据库中增加version字段

    2. 实体类加对应的字段

    //乐观锁version注解 
    @Version  
    private Integer version;
    
    • 1
    • 2
    • 3
    1. 注册组件
    @EnableTransactionManagement
    @Configuration
    public class mybatisplusconfig {
    
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
            mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            return mybatisPlusInterceptor;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    6.查询操作

    1. 单个查询
    @Test
    void  select() {
        User user = userMapper.selectById(1l);
        System.out.println(user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 根据多个id查询
    @Test
    void  select() {
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1,2,3));
        for (User user : users) {
            System.out.println(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    8.删除操作

    1. 根据id删除记录
    @Test
    void  select() {
        int i = userMapper.deleteById(1l);
        System.out.println(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 批量删除
    @Test
    void  select() {
        int i = userMapper.deleteBatchIds(Arrays.asList(2,3,4));
        System.out.println(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 通过Map定制删除
    @Test
    void  select() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","超哥");
        int i = userMapper.deleteByMap(map);
        System.out.println(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    C语言学生成绩管理系统
    vue学习笔记23-组件事件⭐
    Vue3的Teleport:Teleport是Vue3的一个新功能,它允许我们将子组件渲染到父组件以外的地方,这在处理模态框、弹出窗口等情况时非常有用
    Zemax基础知识7--衍射知识(一)
    【Unity】对象池技术
    Sobel算子详解及例程
    win11网络连接正常,但是无法正常上网
    PostgreSQL - 基于pg_basebackup实现主从流复制
    软件系统与熵增
    JavaWeb核心篇(2)——Request和Response
  • 原文地址:https://blog.csdn.net/m0_46813809/article/details/125500255