spring boot原理
自动配置:
pom.xml
- 在写入或者引入一些springboot依赖的时候,不需要指定版本,因为有父项目指定的版本。
启动器:
-
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
-
上面也就是springboot的启动器 -
spring-boot-starter-web也就是针对web的启动器,默认会导入web的依赖或者配置 -
springboot会将所有的功能场景变成一个个的容器 -
我们要使用什么功能,就只需要找到对应的控制器就行starter
主程序:
-
@SpringBootApplication标注这个类是springboot的应用 -
@SpringBootConfiguration
@Configuration
@Component
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
1.yaml
? Spring Boot 提供了大量的自动配置,极大地简化了spring 应用的开发过程,当用户创建了一个 Spring Boot 项目后,即使不进行任何配置,该项目也能顺利的运行起来。当然,用户也可以根据自身的需要使用配置文件修改 Spring Boot 的默认设置。
SpringBoot 默认使用以下 2 种全局的配置文件,其文件名是固定的。
- application.properties
- application.yml
1.yaml简介
? YAML 全称 YAML Ain’t Markup Language,它是一种以数据为中心的标记语言,比 XML 和 JSON 更适合作为配置文件。
? 想要使用 YAML 作为属性配置文件(以 .yml 或 .yaml 结尾),需要将 SnakeYAML 库添加到 classpath 下,Spring Boot 中的 spring-boot-starter-web 或 spring-boot-starter 都对 SnakeYAML 库做了集成, 只要项目中引用了这两个 Starter 中的任何一个,Spring Boot 会自动添加 SnakeYAML 库到 classpath 下。
2.YAML 语法
YAML 的语法如下:
- 使用缩进表示层级关系。
- 缩进时不允许使用 Tab 键,只允许使用空格。
- 缩进的空格数不重要,但同级元素必须左侧对齐。
- 大小写敏感。
例如:
user:
id: 1
name: 我的世界
age: 20
map: {name: 我的世界,age: 1}
3.yaml常用写法
YAML 支持以下三种数据结构:
- 对象:键值对的集合
- 数组:一组按次序排列的值
- 字面量:单个的、不可拆分的值
1.yaml字面量写法
字面量是指单个的,不可拆分的值,例如:数字、字符串、布尔值、以及日期等。
在 YAML 中,使用“key:[空格]value**”**的形式表示一对键值对(空格不能省略),如 name: hello;
字面量直接写在键值对的“value**”**中即可,且默认情况下字符串是不需要使用单引号或双引号的。
- 若字符串使用单引号,则会转义特殊字符。
name: '你好呀\n你好'
输出:
'你好呀\n你好'
- 若字符串使用双引号,则不会转义特殊字符,特殊字符会输出为其本身想表达的含义
name: "你好呀\n你好"
输出
name='你好呀 你好'
2.yaml对象写法
在 YAML 中,对象可能包含多个属性,每一个属性都是一对键值对。
YAML 为对象提供了 2 种写法:
- 普通写法,使用缩进表示对象与属性的层级关系。
website:
name: hello
url: www.xxx.com
- 行内写法:
website: {name: hello,url: www.xxx.com}
3.YAML 数组写法
YAML 使用“-”表示数组中的元素,普通写法如下:
pets:
-dog
-cat
-pig
行内写法:
pets: [dog,cat,pig]
4.复合结构
以上三种数据结构可以任意组合使用,以实现不同的用户需求,例如:
person:
name: zhangsan
age: 30
pets:
-dog
-cat
-pig
car:
name: QQ
child:
name: zhangxiaosan
age: 2
5.yaml组织结构
一个 YAML 文件可以由一个或多个文档组成,文档之间使用“—**”作为分隔符,且个文档相互独立,互不干扰。如果 YAML 文件只包含一个文档,则“—”**分隔符可以省略。
---
website:
name: bianchengbang
url: www.biancheng.net
---
website: {name: bianchengbang,url: www.biancheng.net}
pets:
-dog
-cat
-pig
---
pets: [dog,cat,pig]
name: "zhangsan \n lisi"
---
name: 'zhangsan \n lisi'
2.springboot配置绑定
? 所谓“配置绑定”就是把配置文件中的值与 JavaBean 中对应的属性进行绑定。通常,我们会把一些配置信息(例如,数据库配置)放在配置文件中,然后通过 Java 代码去读取该配置文件,并且把配置文件中指定的配置封装到 JavaBean(实体类) 中。
SpringBoot 提供了以下 2 种方式进行配置绑定:
- 使用 @ConfigurationProperties 注解
- 使用 @Value 注解
1.@ConfigurationProperties
? 通过 Spring Boot 提供的 @ConfigurationProperties 注解,可以将全局配置文件中的配置数据绑定到 JavaBean 中。下面我们以 Spring Boot 项目 helloworld 为例,演示如何通过 @ConfigurationProperties 注解进行配置绑定。
- 在application.yaml中写入
parse:
name: 你好呀你好
age: 20
list:
- 电脑
- 游戏
- 学习
- 在一个java实体类中写入
@Component
@ConfigurationProperties(prefix = "parse")
public class User {
private String name;
private Integer age;
private List<String> list;
}
注意:
- 只有在容器中的组件,才会拥有 SpringBoot 提供的强大功能。如果我们想要使用 @ConfigurationProperties 注解进行配置绑定,那么首先就要保证该对 JavaBean 对象在 IoC 容器中,所以需要用到 @Component 注解来添加组件到容器中。
- JavaBean 上使用了注解 @ConfigurationProperties(prefix = “parse”) ,它表示将这个 JavaBean 中的所有属性与配置文件中以“parse”为前缀的配置进行绑定。
2.@Value
当我们只需要读取配置文件中的某一个配置时,可以通过 @Value 注解获取。
- 利用@Value进行注入值
@Component
public class User {
@Value("${parse.name}")
private String name;
@Value("${parse.age}")
private Integer age;
private List<String> list;
}
注意:
3.@Value 与 @ConfigurationProperties 对比
@Value 和 @ConfigurationProperties 注解都能读取配置文件中的属性值并绑定到 JavaBean 中,但两者存在以下不同。
使用位置不同
- @ConfigurationProperties:标注在 JavaBean 的类名上;
- @Value:标注在 JavaBean 的属性上。
功能不同
- @ConfigurationProperties:用于批量绑定配置文件中的配置;
- @Value:只能一个一个的指定需要绑定的配置。
松散绑定支持不同
@ConfigurationProperties:支持松散绑定(松散语法),例如实体类 Person 中有一个属性为 firstName,那么配置文件中的属性名支持以下写法:
- person.firstName
- person.first-name
- person.first_name
- PERSON_FIRST_NAME
@Vaule:不支持松散绑定。
SpEL 支持不同
- @ConfigurationProperties:不支持 SpEL 表达式;
- @Value:支持 SpEL 表达式。
复杂类型封装
- @ConfigurationProperties:支持所有类型数据的封装,例如 Map、List、Set、以及对象等;
- @Value:只支持基本数据类型的封装,例如字符串、布尔值、整数等类型。
应用场景不同
@Value 和 @ConfigurationProperties 两个注解之间,并没有明显的优劣之分,它们只是适合的应用场景不同而已。
- 若只是获取配置文件中的某项值,则推荐使用 @Value 注解;
- 若专门编写了一个 JavaBean 来和配置文件进行映射,则建议使用 @ConfigurationProperties 注解。
我们在选用时,根据实际应用场景选择合适的注解能达到事半功倍的效果。
4.@PropertySource
如果将所有的配置都集中到 application.properties 或 application.yml 中,那么这个配置文件会十分的臃肿且难以维护,因此我们通常会将与 Spring Boot 无关的配置(例如自定义配置)提取出来,写在一个单独的配置文件中,并在对应的 JavaBean 上使用 @PropertySource 注解指向该配置文件。
下面在resource下创建一个person.properties配置文件,里面写的内容不能与默认的properties的文件或者yaml文件内容重复,或者删除默认里面的文件内容。
student.name=我的世界
student.age=20
student.list=玩耍,电脑,跑步
创建Java实体类
@Component
@PropertySource(value = "classpath:person.properties")
@ConfigurationProperties(prefix = "student")
public class User {
private String name;
private Integer age;
private List<String> list;
}
3.JSR303校验
导入springboot依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
注意: 如果要使用JSR303校验,则必须在要使用的类上添加@Validated注解,表示这个类支持JSR303校验,否则即使在类属性上使用了JSR303的校验注解也是没有用的
JSR303校验的注解规则:
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
4.默认配置文件及文件位置
通常情况下,Spring Boot 在启动时会将 resources 目录下的 application.properties 或 apllication.yml 作为其默认配置文件,我们可以在该配置文件中对项目进行配置,但这并不意味着 Spring Boot 项目中只能存在一个 application.properties 或 application.yml。
1.默认配置文件
Spring Boot 项目中可以存在多个 application.properties 或 apllication.yml。
Spring Boot 启动时会扫描以下 2个位置的 application.properties 或 apllication.yml 文件,并将它们作为 Spring boot 的默认配置文件。
- classpath:/config/ 也就是在java或者resource目录下创建
- classpath:/ 也就是在java或者resource目录下创建
5. Profile(多环境配置)
在实际的项目开发中,一个项目通常会存在多个环境,例如,开发环境、测试环境和生产环境等。不同环境的配置也不尽相同,例如开发环境使用的是开发数据库,测试环境使用的是测试数据库,而生产环境使用的是线上的正式数据库。
Profile 为在不同环境下使用不同的配置提供了支持,我们可以通过激活、指定参数等方式快速切换环境。
1.多Profile 文件形式
Spring Boot 的配置文件共有两种形式:.properties 文件和 .yml 文件,不管哪种形式,它们都能通过文件名的命名形式区分出不同的环境的配置,文件命名格式为:
application-{profile}.properties/yml
其中,{profile} 一般为各个环境的名称或简称,例如 dev、test 和 prod 等等。
properties 配置
在项目的src/main/resources新建四个文件
- application.properties:主配置文件
- application-dev.properties:开发环境配置文件
- application-test.properties:测试环境配置文件
- application-prod.properties:生产环境配置文件
在application.properties文件中,指定默认服务器端口号为 8080,并通过以下配置激活生产环境(prod)的 profile。
# 指定默认端口号
server.port=8080
# 激活指定的profiles文件
spring.profiles.active=test
在 application-dev.properties 中,指定开发环境端口号为 8081,配置如下
# 开发环境
server.port=8081
在 application-test.properties 中,指定测试环境端口号为 8082,配置如下。
# 测试环境
server.port=8082
在 application-prod.properties 中,指定生产环境端口号为 8083,配置如下。
# 生产环境
server.port=8083
即使主配置文件中设置了端口号,但是在主配置文件中设置profile,所以就会使用profile指向的配置文件了,也可以使用yaml后缀的文件。
2.多Profile 文档块模式
在 YAML 配置文件中,可以使用“—”把配置文件分割成了多个文档块,因此我们可以在不同的文档块中针对不同的环境进行不同的配置,并在第一个文档块内对配置进行切换。
在application.yaml文件中配置多文档快吗模式
server:
port: 8080
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8082
spring:
profiles: test
---
server:
port: 8083
spring:
profiles: prod
**注意:**上面的配置是可以进行更换配置,但是不是最优的方案,可以使用下面的配置
server:
port: 8080
spring:
profiles:
active: prod
---
server:
port: 8081
spring:
config:
activate:
on-profile: dev
---
server:
port: 8082
spring:
config:
activate:
on-profile: test
---
server:
port: 8083
spring:
config:
activate:
on-profile: prod
6.springboot Web开发
要解决的问题
- 导入静态资源
- 解决首页的订制
- 模板引擎 Thymeleaf
- 装备扩展springMvc
- 增删改查
- 拦截器
- 国际化
1.静态资源导入问题
在springboot中,静态资源文件存放在resources目录下,其中包含public目录,static目录,resources目录,
按优先级排序:resources>static>public
根据springboot底层描述,可以设置自己的资源目录,需要在配置文件中设置
spring:
mvc:
static-path-pattern:
**注意:**虽然可以设置自己的资源目录,但是默认的目录文件可以支撑我们的开发,所以没必要跟换。
2.首页定制
静态资源文件夹下的所有 index.html 被称为静态首页或者欢迎页,它们会被被 /** 映射,换句话说就是,当我们访问“/”或者“/index.html”时,都会跳转到静态首页(欢迎页)。
在springboot的项目中,所有的网页文件都已html结尾。
- 在没有使用模板引擎的情况下定制首页
我们需要在静态资源的目录下,任何一个目录创建一个index.html文件,但不要在templates下创建,因为这个目录需要在模板引擎下创建才会生效。
- 使用模板引擎创建首页,其实也就是访问/就会跳转
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 完美整合 。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
声明空间
在使用Thymeleaf之间,还需要在HTML标签中声明名称空间,示例代码如下:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
在 html 标签中声明此名称空间,可避免编辑器出现 html 验证错误,但这一步并非必须进行的,即使我们不声明该命名空间,也不影响 Thymeleaf 的使用。
4.定制和扩展springMvc
Spring Boot 抛弃了传统 xml 配置文件,通过配置类(标注 @Configuration 的类,相当于一个 xml 配置文件)以 JavaBean 形式进行相关配置。
Spring Boot 对 Spring MVC 的自动配置可以满足我们的大部分需求,但是我们也可以通过自定义配置类(标注 @Configuration 的类)并实现 WebMvcConfigurer 接口来定制 Spring MVC 配置,例如拦截器、格式化程序、视图控制器等等。
注意: 1.5 及以前是通过继承 WebMvcConfigurerAdapter 抽象类来定制 Spring MVC 配置的,但在 SpringBoot 2.0 后,WebMvcConfigurerAdapter 抽象类就被弃用了,改为实现 WebMvcConfigurer 接口来定制 Spring MvVC 配置。
WebMvcConfigurer 是一个基于 Java 8 的接口,该接口定义了许多与 Spring MVC 相关的方法,其中大部分方法都是 default 类型的,且都是空实现。因此我们只需要定义一个配置类实现 WebMvcConfigurer 接口,并重写相应的方法便可以定制 Spring MVC 的配置。
方法 | 说明 |
---|
configurePathMatch | HandlerMappings 路径的匹配规则。 | 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.定义拦截器
在 Spring Boot 中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口即可。
HandlerInterceptor 接口中定义以下 3 个方法,如下表。
返回类型 | 方法 | 说明 |
---|
Boolean | preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。 | void | postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) | 该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。 | void | afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) | 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。 |
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2.注册拦截器
创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。
@Configuration
public class loginconfig implements WebMvcConfigurer {
@Bean
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
}
}
3.指定拦截规则
修改 配置类中 addInterceptors() 方法的代码,继续指定拦截器的拦截规则,代码如下。
@Configuration
public class loginconfig implements WebMvcConfigurer {
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("注册拦截器");
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/", "/login", "/index.html", "/user/login", "/css/**", "/images/**", "/js/**", "/fonts/**");
}
}
在指定拦截器拦截规则时,调用了两个方法,这两个方法的说明如下:
- addPathPatterns:该方法用于指定拦截路径,例如拦截路径为“/**”,表示拦截所有请求,包括对静态资源的请求。
- excludePathPatterns:该方法用于排除拦截路径,即指定不需要被拦截器拦截的请求。
4.拦截器实现登陆功能
- 创建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>
- 创建主界面main
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>主界面</h1>
</body>
</html>
- 连接器定义
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object name =request.getSession().getAttribute("message");
if(name==null){
response.sendRedirect("/tologin");
return false;
}else{
return true;
}
}
}
- 注册拦截器和扩展springmvc的controller
public class myconfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/tologin","/login");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("main");
registry.addViewController("/tologin").setViewName("login");
}
}
- 创建一个配置类注入myconfig类
@Configuration
public class loginconfig {
@Bean
public myconfig myconfig(){
return new myconfig();
}
}
- 创建controller层
@Controller
public class login {
@RequestMapping("/login")
public String login(@RequestParam("name") String name,@RequestParam("password") String password,
HttpServletRequest request, HttpSession session,Model model){
session.setMaxInactiveInterval(5);
if(name.equals("user")&&password.equals("123")){
session.setAttribute("message",name);
return "redirect:/";
}else {
model.addAttribute("name","密码错误");
return "login";
}
}
}
8.整合JDBC
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 都默认采用整合 Spring Data 的方式进行统一处理,通过大量自动配置,来简化我们对数据访问层的操作,我们只需要进行简单的设置即可实现对书层的访问。
1.导入jdbc启动器
Spring Boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的场景启动器(Starter),场景启动器中整合了该场景下各种可能用到的依赖,让用户摆脱了处理各种依赖和配置的困扰。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
2.导入数据库驱动
JDBC 的场景启动器中并没有导入数据库驱动,我们需要根据自身的需求引入所需的数据库驱动。例如,访问 MySQL 数据库时,需要导入 MySQL 的数据库驱动:mysql-connector-java,示例代码如下。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Spring Boot 默认为数据库驱动程序做了版本仲裁,所以我们在导入数据库驱动时,可以不再声明版本。需要注意的是,数据库驱动的版本必须与数据库的版本相对应。
3.配置数据源
在导入了 JDBC 场景启动器和数据库驱动后,接下来我们就可以在配置文件(application.properties/yml)中配置数据源了,示例代码(application.yml)如下。
spring:
datasource:
username: 用户
password: 密码
url: jdbc:mysql://localHost:3306/数据库名?useSSL=true&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
**注意:**在一些情况下,可能还需要配置时区,否则就会报错
4.测试
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() throws SQLException {
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
如果控制台输出 com.mysql.cj.jdbc.ConnectionImpl@3ebe4ccc表示已经成功
5.JdbcTemplate实现增删改查
在controller层实现增删改查
@Controller
@RestController
public class login {
@Autowired
private JdbcTemplate jdbcTemplate;
@GetMapping("/getuser")
public List<Map<String,Object>> userList(){
String sql="select * from user.user";
List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql);
return mapList;
}
@GetMapping("/adduser/{id}")
public int adduser(@PathVariable Integer id){
String sql="insert into user.user values (?,\"超哥\",'123456')";
return jdbcTemplate.update(sql,id);
}
@GetMapping("/update/{id}")
public int updateUser(@PathVariable Integer id){
String sql="update user.user set name ='hello world',pwd='258' where id=?";
return jdbcTemplate.update(sql,id);
}
@GetMapping("/remove/{id}")
public int removeuser(@PathVariable Integer id){
String sql="delete from user.user where id=?";
return jdbcTemplate.update(sql,id);
}
}
9.整合Druid数据源
Spring Boot 2.x 默认使用 HikariCP 作为数据源,我们只要在项目中导入了 Spring Boot 的 JDBC 场景启动器,便可以使用 HikariCP 数据源获取数据库连接,对数据库进行增删改查等操作。
HikariCP 是目前市面上性能最好的数据源产品,但在实际的开发过程中,企业往往更青睐于另一款数据源产品:Druid,它是目前国内使用范围最广的数据源产品。
Druid 是阿里巴巴推出的一款开源的高性能数据源产品,Druid 支持所有 JDBC 兼容的数据库,包括 Oracle、MySQL、SQL Server 和 H2 等等。Druid 不仅结合了 C3P0、DBCP 和 PROXOOL 等数据源产品的优点,同时还加入了强大的监控功能。通过 Druid 的监控功能,可以实时观察数据库连接池和 SQL 的运行情况,帮助用户及时排查出系统中存在的问题。
Druid 不是 Spring Boot 内部提供的技术,它属于第三方技术,我们可以通过以下两种方式进行整合:
- 自定义整合 Druid
- 通过 starter 整合 Druid
1.引入Druid数据源依赖
同时也要导入jdbc和mysql的驱动
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在application.yaml配置数据源
spring:
datasource:
username: 用户名
password: 密码
url: jdbc:mysql://localHost:3306/数据库?useSSL=true&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
创建测试类
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
}
}
如果控制台显示class com.alibaba.druid.pool.DruidDataSource表示使用的是druid数据源
2.druid后台监控
spring:
datasource:
username: 用户名
password: 密码
url: jdbc:mysql://localHost:3306/数据库
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
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: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
上面除了jdbc的默认配置,其他配置也就是druid的配置。
创建druid的配置类,将spring.datasource导入到druid数据源中
@Configuration
public class myconfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<StatViewServlet>(new StatViewServlet(),"/druid/*");
Map<String,String> initParas = new HashMap<String,String>();
initParas.put("loginUsername","admin");
initParas.put("loginPassword","123456");
initParas.put("allow","");
bean.setInitParameters(initParas);
return bean;
}
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
创建log4j.properties配置文件,实现一个简单的log4j
### set log levels ###
log4j.rootLogger = debug,stdout
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %5p %n
这时候访问localHost:8080/druid就会跳转到后台监控,并输入密码。
10.整合mybatis
MyBatis-Spring-Boot-Starter类似一个中间件,链接Spring Boot和MyBatis,构建基于Spring Boot的MyBatis应用程序。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
- 创建User类
public class User {
private Integer id;
private String name;
private String pwd;
}
- 创建mapper类
@Mapper
@Repository
public interface Usermapper {
List<User> getUSerlist();
int adduser(User user);
int updateuser(Map<String,Object> map);
int removeuser(int id);
}
- 在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>
- 在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
type-aliases-package: com.yc.springdemo.pojo
- 在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);
}
@GetMapping("/update/{id}")
public int updateUser(@PathVariable Integer id){
HashMap<String, Object> map = new HashMap<>();
map.put("uid",id);
map.put("uname","我的世界");
map.put("upwd","kkkk");
return usermapper.updateuser(map);
}
@GetMapping("/remove/{id}")
public int removeuser(@PathVariable Integer id){
return usermapper.removeuser(id);
}
}
11.springboot集成swagger
开发中有很多接口的开发,接口需要配合完整的接口文档才更方便沟通、使用,Swagger是一个用于自动生成在线接口文档的框架,并可在线测试接口,可以很好的跟Spring结合,只需要添加少量的代码和注解即可,而且在接口变动的同时,即可同步修改接口文档,不用再手动维护接口文档。Swagger3是17年推出的最新版本,相比于Swagger2配置更少,使用更方便
我这里使用的最新版本3.0.0的swagger,不在u需要导入swagger2和swagger-ui这两个包,因为在导入3.0.0的swagger启动包后,自动将一些包注入进去。
- 导入swagger的包
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
- yaml配置
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
**注意:**上面这个yaml配置是专门争对springboot和swagger3不兼容解决的,如果出现 Failed to start bean 'documentationPluginsBootstrapper'; 这个错误,那就是要配置yaml,这个问题的主要原因确实是SpringBoot版本过高导致。如果你用的是SpringBoot2.5.x及之前版本是没有问题的。Spring Boot 2.6.X使用PathPatternMatcher匹配路径,Swagger引用的Springfox使用的路径匹配是基于AntPathMatcher的。
1. 主方法添加@EnableOpenApi注解
@EnableOpenApi
@SpringBootApplication
public class SpringdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringdemoApplication.class, args);
}
}
2.swagger3config的配置
首先配置swagger的apiInfo
1, 方法一
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());
}
上面是直接使用的构造方法,结构混乱
- 方法二
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("超哥的网站")
.description("程序员的设计开发")
.contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
.version("v1.0")
.build();
}
方法er显得就很简单,其实在swagger中,主要的也就是方法二中的那几个参数,多的也无济于事
@Configuration
public class swagger3config {
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.enable(true)
.select()
.apis(RequestHandlerSelectors.basePackage("com.yc.springdemo.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("超哥的网站")
.description("程序员的设计开发")
.contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
.version("v1.0")
.build();
}
}
3.不同环境下,swagger的权限
其实也就是,在生产环境下不能访问swagger,测试环境下可以访问swagger
- 使用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
- 在swagger3config配置
@Configuration
public class swagger3config {
@Value("${swagger.enble}")
private boolean enble;
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.enable(enble)
.select()
.apis(RequestHandlerSelectors.basePackage("com.yc.springdemo.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("超哥的网站")
.description("程序员的设计开发")
.contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
.version("v1.0")
.build();
}
}
4.swagger3注解的详解
@Api 用在controller层的请求类上,表示对类的说明
tags="说明该类的作用,可以在UI界面上看到的注解"
value="该参数没什么意义,在UI界面上也看不到,所以不需要配置"
@ApiOperation 用在请求的方法上,说明方法的用途、作用
value="说明方法的用途、作用"
notes="方法的备注说明"
@ApiParam 描述参数
value="参数说明"
required="boolean 是否必须"
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
@ApiModel:用于bean上,描述返回对象
@ApiModelProperty:用在属性上,描述类的属性
12.springboot自带的异步任务
- 同步是阻塞模式,异步是非阻塞模式。
- 同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会—直等待下去,知道收到返回信息才继续执行下去
- 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回式系统会通知进程进行处理,这样可以提高执行的效率。
异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
springboot提供一下两个注解
- 创建service层
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;
- 创建controller层
@Controller
@RestController
public class login {
@Autowired
private userService userService;
@RequestMapping("/login")
public String hello(){
userService.hello();
return "hello" ;
}
}
13.邮件任务
邮件任务简单地说就是一个人向另一个人发送邮件,
导入pom坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
1. 获取第三方的授权码
打开qq邮箱—>设置—>账户—>开启POP3/SMTP服务
2.配置邮件信息
- application.properties
# qq邮箱
spring.mail.username=qq号@qq.com
# 刚刚生成的授权码
spring.mail.password=授权码
# qq邮箱的host
spring.mail.host=smtp.qq.com
#开启加密验证(qq邮箱)
spring.mail.properties.mail.smtp.ssl.enable=true
或者yaml
spring:
mail:
username: qq号@qq.com
password: 授权码
host: smtp.qq.com
properties:
mail:
smtp:
ssl:
enable: true
3.简单邮件发送
调用JavaMailSenderImpl类,使用SimpleMailMessage发送邮件内容
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() throws SQLException {
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("发给你的");
message.setText("你好");
message.setTo("接收方qq号@qq.com");
message.setFrom("发送方qq号@qq.com");
mailSender.send(message);
}
}
4.复杂邮件发送
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() throws SQLException, MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setSubject("复杂邮件");
mimeMessageHelper.setText("<h1>这是一个美女图片</h1>",true);
mimeMessageHelper.addAttachment("美女.png",new File("文件地址"));
mimeMessageHelper.setTo("qq号@qq.com");
mimeMessageHelper.setFrom("qq号@qq.com");
mailSender.send(mimeMessage);
}
}
将上述用到的内容封装成一个方法
public void SendMail(Boolean html, String title, String text, File file, String sendTo, String sendFrom) throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setSubject(title);
mimeMessageHelper.setText(text, html);
mimeMessageHelper.addAttachment("1.jpg", file);
mimeMessageHelper.setTo(sendTo);
mimeMessageHelper.setFrom(sendFrom);
mailSender.send(mimeMessage);
}
14.集成restTemplate
restTemplate底层是基于HttpURLConnection实现的restful风格的接口调用,类似于webservice,rpc远程调用,但其工作模式更加轻量级,方便于rest请求之间的调用,完成数据之间的交互,在springCloud之中也有一席之地。
1.restTemplate常用方法列表
forObeject跟forEntity有什么区别呢?主要的区别是forEntity的功能更加强大一些,其返回值是一个ResponseEntity,更加方便我们获得响应的body,head等信息。exchange方法和其他方法不同之处就是能自己定义的rest请求方式。
- 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等
- 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 |
- put请求方式实现
方法 | 返回值 |
---|
put(String,Object) | void | put(String,Object,Object…) | void | put(String,Object,Map) | void |
- delete请求方法与put方法相似,但没有request参数
- exchange自定义请求方法
暂时不做过多的解释,使用get和post就已经足够
2.rest接口调用实示例
restTemplate配置
restTemplate简单配置如下:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(15000);
factory.setReadTimeout(5000);
return factory;
}
}
4.示例
- get请求接口调用示例
@GetMapping("user")
public String getUser(){
return "youku1327";
}
@GetMapping("user/{name}")
public String getUserName(@PathVariable String name){
return name;
}
GET参数说明
- 第一个参数是url。
- 第二个参数是返回值类型。
- 第三个参数是uri地址路径变量。
@Test
public void testGETNoParams(){
String result = restTemplate.getForObject("http://localhost:8090/youku1327/user", String.class);
System.out.println(result);
}
@Test
public void testGETParams(){
String result = restTemplate.getForObject("http://localhost:8090/youku1327/user/{name}", String.class,"lsc");
System.out.println(result);
}
- post请求接口调用示例
POST请求参数说明
- 第一个参数是url。
- 第二个参数是请求参数。
- 第三个参数是返回值类型。
- 第三个参数是uri地址路径变量。
@PostMapping("provider")
public ResponseEntity<String> addData(@RequestBody JSONObject jsonObject){
String user = (String) jsonObject.get("user");
return ResponseEntity.ok(user);
}
@Test
public void testPostMethod() throws MalformedURLException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("user","youku1327");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
HttpEntity<JSONObject> httpEntity = new HttpEntity(jsonObject,httpHeaders);
String url = "http://localhost:8090/youku1327/provider";
ResponseEntity<String> mapResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
System.out.println(mapResponseEntity.getBody());
}
其他请求的类型访问https://www.jb51.net/article/185208.htm
15定时任务
- TaskSchedulerr 任务调度者接口
- TaskExecutor 任务调度者j接口
- @EnableScheduling 开启定时任务的注解(在启动方法上使用)
- @Scheduled 什么时候执行
16.整合redis数据库
spring boot操作数据:spring-data jpa jdbc mongodb redis
springdata也是spring boot齐名的项目
我们之所以要学习Redis,是要令我们Java程序更加有效率,我们在使用数据库的时候给它加上一个缓存中间件,就是用来提高我们程序的效率的,那么当然,Redis还是要集成到我们SpringBoot项目里面的!!
首先导入redis的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
到我们SpringBoot2.x版本,其内置的Redis中间件再也不是Jedis了,而是换成了lettuce 。我们点进redis依赖就可以发现
在spring-boot-starter-data-redis中使用的是下面的lettuce类型
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.1.8.RELEASE</version>
<scope>compile</scope>
</dependency>
-
lettuce: Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。 -
Jedis: Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加 物理连接。 这种方式更加类似于我们 BIO 一条线程连一个客户端,并且是阻塞式的,会一直连接着客户端等待客户端的命令
RedisAutoConfiguration 分析
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
{
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
RedisProperties配置文件属性
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String username;
private String password;
private int port = 6379;
private boolean ssl;
private Duration timeout;
private Duration connectTimeout;
private String clientName;
private ClientType clientType;
private Sentinel sentinel;
private Cluster cluster;
}
其中主机名和端口号都有默认值,如果我们连自己的电脑,那么这两个配置都可以不用修改!
如果要使用其他远程的数据库,则要使用以下配置
spring:
redis:
port: 6379
password: redis的密码
host: ip地址
1.测试连接
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void contextLoads(){
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
connection.close();
redisTemplate.opsForValue().set("name","nihao");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
**注意:**opsForXXXXX()这个方法是开启某种类型的方法,比如set,hash,list等
2.自定义RedisTemplate
那么现在我们能在SpringBoot项目中使用Redis了,但是我们如果存中文的话会发生乱码!
我们在分析Redis配置文件中也发现了我们的RedisTemplate时可以让我们配置的,那么默认的RedisTemplate给我们配置了什么?
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
.......
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer hashValueSerializer = null;
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<>(this);
}
initialized = true;
}
}
我们可以知道,如果我们不配置RedisTemplate的序列化方式,默认的是使用JDK中的序列化方式,那么我们点进这个JdkSerializationRedisSerializer类中看看
点进深层我们发现,里面其实创建了一个序列化转换器,而转换器默认的构造方法是JDK默认的序列化工具,其实现了Serializer接口
void serialize(T object, OutputStream outputStream) throws IOException;
因为我们java使用ISO-8859-1编码进行传输数据的,那么我们传输字符串的话,那么编解码会不一致,一定会出现乱码!!!
我们从上面分析可以知道,当我们的类中出现以redisTemplate 命名的bean的时候,SpringBoot的配置将不会生效!
- 只修改字符编码错误,不能被序列化
@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;
}
这样修改也可以是字符编码不出现乱码,但这不是唯一的解决办法。
- 接管一下这个
redisTemplate 类, 配置自己的序列化
@Configuration
public class myConfig {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
Jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
template.setValueSerializer(Jackson2JsonRedisSerializer);
template.setHashValueSerializer(Jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
3.封装自己的redisutil类
我们在常常使用redis的时候,需要反复的使用同一个和方法,或者存储一个数据可能需要好几步,这时候哦我们呢就会封装一个库
@Component
public final class Redisutil {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
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;
}
}
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@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));
}
}
}
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return 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;
}
}
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return 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;
}
}
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;
}
}
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;
}
}
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
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;
}
}
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
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;
}
}
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
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;
}
}
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
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;
}
}
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;
}
}
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
网址:https://www.jianshu.com/p/2909ee88cabb
17.整合fastjson(响应)
springboot默认使用jackson作为json解析框架,如果使用fastjson,可以按照下列方式配置使用
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
注: fastjson爆出远程代码执行高危漏洞,影响范围FastJSON 1.2.30及以下版本,FastJSON 1.2.41至1.2.45版本;官方建议升级至FastJSON最新版本,建议升级至1.2.58版本。
1.在配置类中配置
一.继承方式
继承WebMvcConfigurerAdapter类,重写configureMessageConverters方法;在springboot2.x版本中WebMvcConfigurerAdapter类已经过时了
@Configuration
@EnableWebMvc
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastConverter.setFastJsonConfig(fastJsonConfig);
converters.add(fastConverter);
}
}
二.通过@Bean方式注入
注入Bean : HttpMessageConverters
@Configuration
public class HttpConverterConfig {
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON);
fastConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastConverter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = fastConverter;
return new HttpMessageConverters(converter);
}
}
2.fastjson中SerializerFeature的用法及中文注解
package com.alibaba.fastjson.serializer;
public enum SerializerFeature {
QuoteFieldNames,
UseSingleQuotes,
WriteMapNullValue,
WriteEnumUsingToString,
UseISO8601DateFormat,
WriteNullListAsEmpty,
WriteNullStringAsEmpty,
WriteNullNumberAsZero,
WriteNullBooleanAsFalse,
SkipTransientField,
SortField,
@Deprecated
WriteTabAsSpecial,
PrettyFormat,
WriteClassName,
DisableCircularReferenceDetect,
WriteSlashAsSpecial,
BrowserCompatible,
WriteDateUseDateFormat,
NotWriteRootClassName,
DisableCheckSpecialChar,
BeanToArray
;
private SerializerFeature(){
mask = (1 << ordinal());
}
private final int mask;
public final int getMask() {
return mask;
}
public static boolean isEnabled(int features, SerializerFeature feature) {
return (features & feature.getMask()) != 0;
}
public static int config(int features, SerializerFeature feature, boolean state) {
if (state) {
features |= feature.getMask();
} else {
features &= ~feature.getMask();
}
return features;
}
}
3.Fastjson反序列化java对象
后面在常使用json时,我们可能要把Json转化为java对象,这时候就要使用下面的方法
Object name = redisutil.get("name");
String jsonObject = JSON.toJSONString(name);
User user = JSON.parseObject(jsonObject,User.class);
18.整合mybatis-plus
MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- 创建数据库
- 创建一个表,写入数据
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)
);
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');
- 搭建springboot环境
- 导入pom坐标
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时带入mybatis跟mybatis-plus,可能会产生冲突
- 连接数据库,这一步与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
- 创建实体类,借助lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- 创就mapper接口,并且继承BaseMapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
- 创建测试类
@SpringBootTest
class MybatisplusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
}
- mybatis-plus的基本配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.yc.mybatisplus.pojo
1.主键生成策略
分布式系统唯一ID生成方案:https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
主键自增
我们需要配置主键自增:
1、实体类字段上增加 @TableId(type = IdType.AUTO)
2、数据库字段一定要是设置自增的!
源码解释
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ID_WORKER(3),
UUID(4),
ID_WORKER_STR(5);
}
改为手动输入之后,就需要自己配置id
public class User {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private Integer age;
private String email;
}
2.插入操作
@Test
public void testInsert(){
User user = new User();
user.setName("小爽帅到拖网速");
user.setAge(20);
user.setEmail("1372713212@qq.com");
int result = userMapper.insert(user);
System.out.println(result);
System.out.println(user);
}
3.更新操作
@Test
void update() {
User user = new User();
user.setId(6l);
user.setAge(30);
user.setName("超爷");
user.setEmail("3222@QQ.COM");
int update = userMapper.updateById(user);
System.out.println(update);
}
4.自动填充
创建时间、修改时间!这些个操作一般都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create 、gmt_modify几乎所有的表都要配置上,而且需要自动化!
方式一:数据库级别的修改 (工作中是不允许你修改数据库)
- 在表中新增字段create_time、update_time
- 再次测试插入方法,我们需要先把实体类同步!
@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;
}
方式二,代码级别
- 删除数据库的默认值,更新操作,也就是根据当前时间戳更新关闭
- 实体类字段属性上需要增加注解
@TableField(fill = FieldFill.INSERT)
private Date create_time;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date update_time;
- 编写处理器来处理这个注解即可!
由于这个处理器在Springboot下面, mybatis会自动处理我们写的所有的处理器
当我们执行插入操作的时候,自动帮我们通过反射去读取哪边有对应注解的字段,从而把处理器代码插入成功,会自动帮我把createTime,updateTime插入值
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill.....");
this.setFieldValByName("create_time",new Date(),metaObject);
this.setFieldValByName("update_time",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill.....");
this.setFieldValByName("update_time",new Date(),metaObject);
}
}
注意:处理器指定字段中大写字母不能被识别成-小写字母的格式
5.乐观锁
在面试过程中,我们经常会被问道乐观锁,悲观锁,其实原理非常简单
乐观锁 OptimisticLockerInnerInterceptor
顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁!如果出现问题就再次更新测试
这里引出 旧version 新version
乐观锁:当要更新一条记录的时候,希望这条记录没有被别人更新 乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败‘
乐观锁:1、先查询,获得版本号 version = 1
update user set name = "xiaoshaung",version = version + 1
where id = 2 and version = 1
update user set name = "小爽",version = version + 1
where id = 2 and version = 1
-
给数据库中增加version字段 -
实体类加对应的字段
@Version
private Integer version;
- 注册组件
@EnableTransactionManagement
@Configuration
public class mybatisplusconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
- 单线程下测试
@Test
void update() {
User user = userMapper.selectById(8l);
user.setName("你好超哥");
user.setEmail("366666@qqq.com");
int i = userMapper.updateById(user);
System.out.println(i);
}
- 多线程测试
@Test
public void testOptimisticLock2(){
User user = userMapper.selectById(1);
user.setName("xiaoshaung111");
user.setEmail("123123132@qq.com");
User user2 = userMapper.selectById(1);
user2.setName("xiaoshaung222");
user2.setEmail("123123132@qq.com");
userMapper.updateById(user2);
userMapper.updateById(user);
}
6.查询操作
- 单个查询
@Test
void select() {
User user = userMapper.selectById(1l);
System.out.println(user);
}
- 根据多个id查询
@Test
void select() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1,2,3));
for (User user : users) {
System.out.println(user);
}
}
- 条件查询map
@Test
void select() {
HashMap<String, Object> map = new HashMap<>();
map.put("name","超哥");
map.put("age","30");
List<User> users = userMapper.selectByMap(map);
for (User user : users) {
System.out.println(user);
}
}
7.分页查询
分页在网站使用的十分之多!
- 原始的limit 进行分页
- pageHepler 第三方插件
- Mybatis-Plus其实也内置了分页插件!
mybatisplus实现分页
- 拦截器组件即可
@EnableTransactionManagement
@Configuration
public class mybatisplusconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
}
- 直接使用page对象即可
@Test
public void testPage(){
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
System.out.println("getCurrent()"+page.getCurrent());
System.out.println("page.getSize()"+page.getSize());
System.out.println("page.getTotal()"+page.getTotal());
}
8.删除操作
- 根据id删除记录
@Test
void select() {
int i = userMapper.deleteById(1l);
System.out.println(i);
}
- 批量删除
@Test
void select() {
int i = userMapper.deleteBatchIds(Arrays.asList(2,3,4));
System.out.println(i);
}
- 通过Map定制删除
@Test
void select() {
HashMap<String, Object> map = new HashMap<>();
map.put("name","超哥");
int i = userMapper.deleteByMap(map);
System.out.println(i);
}
|