自动配置:
pom.xml
启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
上面也就是springboot的启动器
spring-boot-starter-web也就是针对web的启动器,默认会导入web的依赖或者配置
springboot会将所有的功能场景变成一个个的容器
我们要使用什么功能,就只需要找到对应的控制器就行starter
主程序:
@SpringBootApplication标注这个类是springboot的应用
@SpringBootConfiguration //springboot的配置
@Configuration //spring的配置
@Component //说明这是一个spring的组件
@EnableAutoConfiguration //自动配置
@AutoConfigurationPackage //自动配置包
@Import(AutoConfigurationPackages.Registrar.class) //导入选择器
Spring Boot 提供了大量的自动配置,极大地简化了spring 应用的开发过程,当用户创建了一个 Spring Boot 项目后,即使不进行任何配置,该项目也能顺利的运行起来。当然,用户也可以根据自身的需要使用配置文件修改 Spring Boot 的默认设置。
SpringBoot 默认使用以下 2 种全局的配置文件,其文件名是固定的。
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 下。
YAML 的语法如下:
例如:
user:
id: 1
name: 我的世界
age: 20
map: {name: 我的世界,age: 1}
YAML 支持以下三种数据结构:
字面量是指单个的,不可拆分的值,例如:数字、字符串、布尔值、以及日期等。
在 YAML 中,使用“key:[空格]value**”**的形式表示一对键值对(空格不能省略),如 name: hello;
字面量直接写在键值对的“value**”**中即可,且默认情况下字符串是不需要使用单引号或双引号的。
name: '你好呀\n你好'
输出:
'你好呀\n你好'
name: "你好呀\n你好"
输出
name='你好呀 你好'
在 YAML 中,对象可能包含多个属性,每一个属性都是一对键值对。
YAML 为对象提供了 2 种写法:
website:
name: hello
url: www.xxx.com
website: {name: hello,url: www.xxx.com}
YAML 使用“-”表示数组中的元素,普通写法如下:
pets:
-dog
-cat
-pig
行内写法:
pets: [dog,cat,pig]
以上三种数据结构可以任意组合使用,以实现不同的用户需求,例如:
person:
name: zhangsan
age: 30
pets:
-dog
-cat
-pig
car:
name: QQ
child:
name: zhangxiaosan
age: 2
一个 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'
所谓“配置绑定”就是把配置文件中的值与 JavaBean 中对应的属性进行绑定。通常,我们会把一些配置信息(例如,数据库配置)放在配置文件中,然后通过 Java 代码去读取该配置文件,并且把配置文件中指定的配置封装到 JavaBean(实体类) 中。
SpringBoot 提供了以下 2 种方式进行配置绑定:
通过 Spring Boot 提供的 @ConfigurationProperties 注解,可以将全局配置文件中的配置数据绑定到 JavaBean 中。下面我们以 Spring Boot 项目 helloworld 为例,演示如何通过 @ConfigurationProperties 注解进行配置绑定。
parse:
name: 你好呀你好
age: 20
list:
- 电脑
- 游戏
- 学习
/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
*
* @ConfigurationProperties:告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "parse":配置文件中哪个下面的所有属性进行一一映射
*
* 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能;
*/
@Component
@ConfigurationProperties(prefix = "parse")
public class User {
private String name;
private Integer age;
private List<String> list;
//其他方法省略
}
注意:
当我们只需要读取配置文件中的某一个配置时,可以通过 @Value 注解获取。
@Component
public class User {
@Value("${parse.name}")
private String name;
@Value("${parse.age}")
private Integer age;
private List<String> list;
//省略其他方法
}
注意:
@Value 和 @ConfigurationProperties 注解都能读取配置文件中的属性值并绑定到 JavaBean 中,但两者存在以下不同。
使用位置不同
功能不同
松散绑定支持不同
@ConfigurationProperties:支持松散绑定(松散语法),例如实体类 Person 中有一个属性为 firstName,那么配置文件中的属性名支持以下写法:
@Vaule:不支持松散绑定。
SpEL 支持不同
复杂类型封装
应用场景不同
@Value 和 @ConfigurationProperties 两个注解之间,并没有明显的优劣之分,它们只是适合的应用场景不同而已。
我们在选用时,根据实际应用场景选择合适的注解能达到事半功倍的效果。
如果将所有的配置都集中到 application.properties 或 application.yml 中,那么这个配置文件会十分的臃肿且难以维护,因此我们通常会将与 Spring Boot 无关的配置(例如自定义配置)提取出来,写在一个单独的配置文件中,并在对应的 JavaBean 上使用 @PropertySource 注解指向该配置文件。
下面在resource下创建一个person.properties配置文件,里面写的内容不能与默认的properties的文件或者yaml文件内容重复,或者删除默认里面的文件内容。
student.name=我的世界
student.age=20
student.list=玩耍,电脑,跑步
创建Java实体类
@Component
@PropertySource(value = "classpath:person.properties")
/*
再导入其他配置文件后,可以不使用ConfigurationProperties注解绑定值,
可以使用@Value以点的形式指向属性,但是数组,集合不能使用
*/
@ConfigurationProperties(prefix = "student")
public class User {
private String name;
private Integer age;
private List<String> list;
}
导入springboot依赖
<!--JSR303校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
注意: 如果要使用JSR303校验,则必须在要使用的类上添加@Validated注解,表示这个类支持JSR303校验,否则即使在类属性上使用了JSR303的校验注解也是没有用的
JSR303校验的注解规则:
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 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=)
通常情况下,Spring Boot 在启动时会将 resources 目录下的 application.properties 或 apllication.yml 作为其默认配置文件,我们可以在该配置文件中对项目进行配置,但这并不意味着 Spring Boot 项目中只能存在一个 application.properties 或 application.yml。
Spring Boot 项目中可以存在多个 application.properties 或 apllication.yml。
Spring Boot 启动时会扫描以下 2个位置的 application.properties 或 apllication.yml 文件,并将它们作为 Spring boot 的默认配置文件。
在实际的项目开发中,一个项目通常会存在多个环境,例如,开发环境、测试环境和生产环境等。不同环境的配置也不尽相同,例如开发环境使用的是开发数据库,测试环境使用的是测试数据库,而生产环境使用的是线上的正式数据库。
Profile 为在不同环境下使用不同的配置提供了支持,我们可以通过激活、指定参数等方式快速切换环境。
Spring Boot 的配置文件共有两种形式:.properties 文件和 .yml 文件,不管哪种形式,它们都能通过文件名的命名形式区分出不同的环境的配置,文件命名格式为:
application-{profile}.properties/yml
其中,{profile} 一般为各个环境的名称或简称,例如 dev、test 和 prod 等等。
properties 配置
在项目的src/main/resources新建四个文件
在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后缀的文件。
在 YAML 配置文件中,可以使用“—”把配置文件分割成了多个文档块,因此我们可以在不同的文档块中针对不同的环境进行不同的配置,并在第一个文档块内对配置进行切换。
在application.yaml文件中配置多文档快吗模式
# 默认配置
server:
port: 8080
#更改配置为dev
spring:
profiles:
active: dev
---
#开发环境
server:
port: 8081
spring:
profiles: dev
---
#测试环境
server:
port: 8082
spring:
profiles: test
---
#生产环境
server:
port: 8083
spring:
profiles: prod
**注意:**上面的配置是可以进行更换配置,但是不是最优的方案,可以使用下面的配置
# 默认配置
server:
port: 8080
#更换配置
spring:
profiles:
active: prod
---
# 开发配置
server:
port: 8081
spring:
config:
activate:
on-profile: dev
---
# 测试环境
server:
port: 8082
spring:
config:
activate:
on-profile: test
---
#生产环境
server:
port: 8083
spring:
config:
activate:
on-profile: prod
要解决的问题
在springboot中,静态资源文件存放在resources目录下,其中包含public目录,static目录,resources目录,
按优先级排序:resources>static>public
根据springboot底层描述,可以设置自己的资源目录,需要在配置文件中设置
spring:
mvc:
static-path-pattern: #自己的资源目录
**注意:**虽然可以设置自己的资源目录,但是默认的目录文件可以支撑我们的开发,所以没必要跟换。
静态资源文件夹下的所有 index.html 被称为静态首页或者欢迎页,它们会被被 /** 映射,换句话说就是,当我们访问“/”或者“/index.html”时,都会跳转到静态首页(欢迎页)。
在springboot的项目中,所有的网页文件都已html结尾。
我们需要在静态资源的目录下,任何一个目录创建一个index.html文件,但不要在templates下创建,因为这个目录需要在模板引擎下创建才会生效。
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 特点
Spring Boot 推荐使用 Thymeleaf 作为其模板引擎。SpringBoot 为 Thymeleaf 提供了一系列默认配置,项目中一但导入了 Thymeleaf 的依赖,相对应的自动配置 (ThymeleafAutoConfiguration) 就会自动生效,因此 Thymeleaf 可以与 Spring Boot 完美整合 。
引入依赖
<!--Thymeleaf 启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
声明空间
在使用Thymeleaf之间,还需要在HTML标签中声明名称空间,示例代码如下:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
在 html 标签中声明此名称空间,可避免编辑器出现 html 验证错误,但这一步并非必须进行的,即使我们不声明该命名空间,也不影响 Thymeleaf 的使用。
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 错误。
我们对拦截器并不陌生,无论是 Struts 2 还是 Spring MVC 中都提供了拦截器功能,它可以根据 URL 对请求进行拦截,主要应用于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能上。Spring Boot 同样提供了拦截器功能。
在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:
在 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);
}
}
创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。
@Configuration
public class loginconfig implements WebMvcConfigurer {
@Bean
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
}
}
修改 配置类中 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/**"); //放行登录页,登陆操作,静态资源
}
}
在指定拦截器拦截规则时,调用了两个方法,这两个方法的说明如下:
<!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>
<!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 {
//获取session的message的值
Object name =request.getSession().getAttribute("message");
if(name==null){
//如果session为空,则返回到登录页
response.sendRedirect("/tologin");
return false;
}else{
return true;
}
}
}
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");
}
}
@Configuration
public class loginconfig {
@Bean
public myconfig myconfig(){
return new myconfig();
}
}
@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";
}
}
}
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 都默认采用整合 Spring Data 的方式进行统一处理,通过大量自动配置,来简化我们对数据访问层的操作,我们只需要进行简单的设置即可实现对书层的访问。
Spring Boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的场景启动器(Starter),场景启动器中整合了该场景下各种可能用到的依赖,让用户摆脱了处理各种依赖和配置的困扰。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
JDBC 的场景启动器中并没有导入数据库驱动,我们需要根据自身的需求引入所需的数据库驱动。例如,访问 MySQL 数据库时,需要导入 MySQL 的数据库驱动:mysql-connector-java,示例代码如下。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Spring Boot 默认为数据库驱动程序做了版本仲裁,所以我们在导入数据库驱动时,可以不再声明版本。需要注意的是,数据库驱动的版本必须与数据库的版本相对应。
在导入了 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
**注意:**在一些情况下,可能还需要配置时区,否则就会报错
@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表示已经成功
在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);
}
}
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 内部提供的技术,它属于第三方技术,我们可以通过以下两种方式进行整合:
同时也要导入jdbc和mysql的驱动
<!--采用自定义方式整合 druid 数据源-->
<!--自定义整合需要编写一个与之相关的配置类-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在application.yaml配置数据源
spring:
datasource:
username: 用户名
password: 密码
url: jdbc:mysql://localHost:3306/数据库?useSSL=true&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
创建测试类
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
}
}
如果控制台显示class com.alibaba.druid.pool.DruidDataSource表示使用的是druid数据源
spring:
datasource:
username: 用户名
password: 密码
url: jdbc:mysql://localHost:3306/数据库
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#自动往数据库建表
# schema:
# - classpath:department.sql
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
上面除了jdbc的默认配置,其他配置也就是druid的配置。
创建druid的配置类,将spring.datasource导入到druid数据源中
@Configuration
public class myconfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
//因为Springboot内置了servlet容器,所以没有web.xml,替代方法就是将ServletRegistrationBean注册进去
//加入后台监控
@Bean //这里其实就相当于servlet的web.xml
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<StatViewServlet>(new StatViewServlet(),"/druid/*");
//后台需要有人登录,进行配置
//bean.addUrlMappings(); 这个可以添加映射,我们在构造里已经写了
//设置一些初始化参数
Map<String,String> initParas = new HashMap<String,String>();
initParas.put("loginUsername","admin");//它这个账户密码是固定的
initParas.put("loginPassword","123456");
//允许谁能防伪
initParas.put("allow","");//这个值为空或没有就允许所有人访问,ip白名单
//initParas.put("allow","localhost");//只允许本机访问,多个ip用逗号,隔开
//initParas.put("deny","");//ip黑名单,拒绝谁访问 deny和allow同时存在优先deny
// initParas.put("resetEnable","false");//禁用HTML页面的Reset按钮
bean.setInitParameters(initParas);
return bean;
}
//2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
创建log4j.properties配置文件,实现一个简单的log4j
### set log levels ###
log4j.rootLogger = debug,stdout
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %5p %n
这时候访问localHost:8080/druid就会跳转到后台监控,并输入密码。
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>
public class User {
private Integer id;
private String name;
private String pwd;
//其他方法省略
}
@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);
}
<?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>
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 #将扫描包下的内容起别名
@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);
}
}
开发中有很多接口的开发,接口需要配合完整的接口文档才更方便沟通、使用,Swagger是一个用于自动生成在线接口文档的框架,并可在线测试接口,可以很好的跟Spring结合,只需要添加少量的代码和注解即可,而且在接口变动的同时,即可同步修改接口文档,不用再手动维护接口文档。Swagger3是17年推出的最新版本,相比于Swagger2配置更少,使用更方便
我这里使用的最新版本3.0.0的swagger,不在u需要导入swagger2和swagger-ui这两个包,因为在导入3.0.0的swagger启动包后,自动将一些包注入进去。
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
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的。
@EnableOpenApi //相当于开启swagger3的API,其实不开启也行
@SpringBootApplication
public class SpringdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringdemoApplication.class, args);
}
}
首先配置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());
}
上面是直接使用的构造方法,结构混乱
//配置swagger的apiInfo
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("超哥的网站")
.description("程序员的设计开发")
.contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
.version("v1.0")
.build();
}
方法er显得就很简单,其实在swagger中,主要的也就是方法二中的那几个参数,多的也无济于事
@Configuration
public class swagger3config {
@Bean //配置swagger的coket的bean实例
public Docket docket(){
//swagger3使用DocumentationType.OAS_30,swagger2使用DocumentationType SWAGGER_2
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.enable(true) //开启swagger,默认为true,禁用就不能访问swagger
.select()
/**
* apis方法是select()下的方法,里面可以有以下方式
* 1. RequestHandlerSelectors 用于扫描接口的方式
* 2. .any()扫描全部
* 3. .none()不扫描
* 4. .withClassAnnotation()扫描一个类上的注解,只有存在这个注解才会被扫描
* 5. 。withMethodAnnotation()扫描方法上的注解,只有这个方法上的注解存在才会被扫描
* 6. .basePackage()扫描指定的包
*/
.apis(RequestHandlerSelectors.basePackage("com.yc.springdemo.controller"))
/** 过滤接口
* 1. PathSelectors扫描指定的接口
* 2. .ant("/yc/"")过滤yc下的所有指定路径
* 3. .any()所有的路径
* 4. 。none()不过滤任何路径
* 5. 。regex()通过正则表达式过滤接口
*/
.paths(PathSelectors.any())
.build();
}
//配置swagger的apiInfo
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("超哥的网站")
.description("程序员的设计开发")
.contact(new Contact("超哥","https://www.baidu.com","3226872969@qq.com"))
.version("v1.0")
.build();
}
}
其实也就是,在生产环境下不能访问swagger,测试环境下可以访问swagger
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
@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();
}
}
@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:用在属性上,描述类的属性
异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
springboot提供一下两个注解
@Async 用在方法上,表示异步任务
@EnableAsync 用在主方法上,表示开启异步支持
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;
@Controller
@RestController
public class login {
@Autowired
private userService userService;
@RequestMapping("/login")
public String hello(){
userService.hello();
return "hello" ;
}
}
邮件任务简单地说就是一个人向另一个人发送邮件,
导入pom坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
打开qq邮箱—>设置—>账户—>开启POP3/SMTP服务
# 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
调用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);
}
}
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() throws SQLException, MessagingException {
//复杂邮件创建
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组合创建复杂邮件内容
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
//邮件标题
mimeMessageHelper.setSubject("复杂邮件");
//邮件内容,true代表支持html格式
mimeMessageHelper.setText("<h1>这是一个美女图片</h1>",true);
//发送图片
mimeMessageHelper.addAttachment("美女.png",new File("文件地址"));
//接收方
mimeMessageHelper.setTo("qq号@qq.com");
//发送方
mimeMessageHelper.setFrom("qq号@qq.com");
//发送
mailSender.send(mimeMessage);
}
}
将上述用到的内容封装成一个方法
public void SendMail(Boolean html, String title, String text, File file, String sendTo, String sendFrom) throws MessagingException {
//复杂邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setSubject(title);
mimeMessageHelper.setText(text, html);//true,开启html解析
mimeMessageHelper.addAttachment("1.jpg", file);
mimeMessageHelper.setTo(sendTo);
mimeMessageHelper.setFrom(sendFrom);
mailSender.send(mimeMessage);
}
restTemplate底层是基于HttpURLConnection实现的restful风格的接口调用,类似于webservice,rpc远程调用,但其工作模式更加轻量级,方便于rest请求之间的调用,完成数据之间的交互,在springCloud之中也有一席之地。
forObeject跟forEntity有什么区别呢?主要的区别是forEntity的功能更加强大一些,其返回值是一个ResponseEntity,更加方便我们获得响应的body,head等信息。exchange方法和其他方法不同之处就是能自己定义的rest请求方式。
| 方法 | 返回值 |
|---|---|
| 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方法详解
| 方法 | 返回值 |
|---|---|
| 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(String,Object) | void |
| put(String,Object,Object…) | void |
| put(String,Object,Map) | void |
暂时不做过多的解释,使用get和post就已经足够
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;
}
}
@GetMapping("user")
public String getUser(){
return "youku1327";
}
@GetMapping("user/{name}")
public String getUserName(@PathVariable String name){
return name;
}
GET参数说明
@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);
}
POST请求参数说明
@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
spring boot操作数据:spring-data jpa jdbc mongodb redis
springdata也是spring boot齐名的项目
我们之所以要学习Redis,是要令我们Java程序更加有效率,我们在使用数据库的时候给它加上一个缓存中间件,就是用来提高我们程序的效率的,那么当然,Redis还是要集成到我们SpringBoot项目里面的!!
首先导入redis的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
到我们SpringBoot2.x版本,其内置的Redis中间件再也不是Jedis了,而是换成了lettuce。我们点进redis依赖就可以发现
在spring-boot-starter-data-redis中使用的是下面的lettuce类型
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.1.8.RELEASE</version>
<scope>compile</scope>
</dependency>
lettuce: Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。
Jedis: Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加 物理连接。 这种方式更加类似于我们 BIO 一条线程连一个客户端,并且是阻塞式的,会一直连接着客户端等待客户端的命令
RedisAutoConfiguration 分析
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) //配置文件属性
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
// redis的模板实例,可以帮助我们快速操作redis
@Bean
// 如果这个名为redisTemplate实例不存在的话,该bean生效,那么我们可以代替SpringBoot,自定义我们的redis模板实例!!!
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
// 因为我们很多情况都是操作String类型的数据,那么这个类帮我们也配置了String的redis模板供我们使用!
// 其方法与上面的redisTemplate一样
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
{
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
RedisProperties配置文件属性
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
/**
* 可以配置使用的db下标
*/
private int database = 0;
/**
* 这个配置可以让我们连接到远程的redis中。例如:
* redis://user:password@example.com:6379
*/
private String url;
/**
* Redis服务端的主机名
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis的端口号
*/
private int port = 6379;
/**
* 是否开启安全认证
*/
private boolean ssl;
/**
* Read timeout.
*/
private Duration timeout;
/**
* Connection timeout.
*/
private Duration connectTimeout;
/**
* Client name to be set on connections with CLIENT SETNAME.
*/
private String clientName;
/**
* Type of client to use. By default, auto-detected according to the classpath.
*/
private ClientType clientType;
private Sentinel sentinel;
private Cluster cluster;
}
其中主机名和端口号都有默认值,如果我们连自己的电脑,那么这两个配置都可以不用修改!
如果要使用其他远程的数据库,则要使用以下配置
spring:
redis:
port: 6379 #端口号
# database: 0
password: redis的密码
host: ip地址
@SpringBootTest
class SpringdemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void contextLoads(){
//获取redis数据库连接对象
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
//关闭这个连接
connection.close();
//添加key为String的value
redisTemplate.opsForValue().set("name","nihao");
//获取这个value
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
**注意:**opsForXXXXX()这个方法是开启某种类型的方法,比如set,hash,list等
那么现在我们能在SpringBoot项目中使用Redis了,但是我们如果存中文的话会发生乱码!
我们在分析Redis配置文件中也发现了我们的RedisTemplate时可以让我们配置的,那么默认的RedisTemplate给我们配置了什么?
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
.......
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes")
private @Nullable RedisSerializer hashValueSerializer = null;
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
// 默认的序列化方式为JDK中自带的序列化
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
// 因为我们并没有设置其序列化的实例,那么在创建这个RedisTemplate实例的时候,全部都默认成了JDK的序列化方式!
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<>(this);
}
initialized = true;
}
}
我们可以知道,如果我们不配置RedisTemplate的序列化方式,默认的是使用JDK中的序列化方式,那么我们点进这个JdkSerializationRedisSerializer类中看看
点进深层我们发现,里面其实创建了一个序列化转换器,而转换器默认的构造方法是JDK默认的序列化工具,其实现了Serializer接口
void serialize(T object, OutputStream outputStream) throws IOException;
因为我们java使用ISO-8859-1编码进行传输数据的,那么我们传输字符串的话,那么编解码会不一致,一定会出现乱码!!!
我们从上面分析可以知道,当我们的类中出现以redisTemplate命名的bean的时候,SpringBoot的配置将不会生效!
@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,这样我们才能全面接管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;
}
}
我们在常常使用redis的时候,需要反复的使用同一个和方法,或者存储一个数据可能需要好几步,这时候哦我们呢就会封装一个库
@Component
public final class Redisutil {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
网址:https://www.jianshu.com/p/2909ee88cabb
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版本。
一.继承方式
继承WebMvcConfigurerAdapter类,重写configureMessageConverters方法;在springboot2.x版本中WebMvcConfigurerAdapter类已经过时了
@Configuration
@EnableWebMvc
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
/**
* 配置FastJson为方式一
* @return*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
/*
* 1、需要先定义一个convert转换消息的对象 2、添加fastJson的配置信息,比如:是否要格式化返回json数据 3、在convert中添加配置信息
* 4、将convert添加到converters当中
*
*/
// 1、需要先定义一个·convert转换消息的对象;
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 2、添加fastjson的配置信息,比如 是否要格式化返回json数据
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
// 3、在convert中添加配置信息.
fastConverter.setFastJsonConfig(fastJsonConfig);
// 4、将convert添加到converters当中.
converters.add(fastConverter);
}
}
二.通过@Bean方式注入
注入Bean : HttpMessageConverters
@Configuration
public class HttpConverterConfig {
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
// 1.定义一个converters转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
//解决中文乱码问题:在方法内部添加这段代码
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON);
fastConverter.setSupportedMediaTypes(fastMediaTypes);
// 3.在converter中添加配置信息
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastConverter.setFastJsonConfig(fastJsonConfig);
// 4.将converter赋值给HttpMessageConverter
HttpMessageConverter<?> converter = fastConverter;
// 5.返回HttpMessageConverters对象
return new HttpMessageConverters(converter);
}
}
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;
}
}
后面在常使用json时,我们可能要把Json转化为java对象,这时候就要使用下面的方法
//这个是从redis中获取的json字符串
Object name = redisutil.get("name");
//先转成json对象
String jsonObject = JSON.toJSONString(name);
//转换成java对象
User user = JSON.parseObject(jsonObject,User.class);
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)
);
-- 在真实开发中:会存在 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');
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时带入mybatis跟mybatis-plus,可能会产生冲突
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
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
@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:
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 #别名
分布式系统唯一ID生成方案:https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
主键自增
我们需要配置主键自增:
1、实体类字段上增加 @TableId(type = IdType.AUTO)
2、数据库字段一定要是设置自增的!
源码解释
public enum IdType {
AUTO(0), // 数据库id自增
NONE(1), // 未设置主键
INPUT(2), // 手动输入
ID_WORKER(3), // 默认的全局id
UUID(4), // 全局唯一id
ID_WORKER_STR(5); // ID_WORKER 字符串表示法
}
改为手动输入之后,就需要自己配置id
public class User {
// 对应数据库的主键(uuid、自增id、雪花算法、redis、zookeeper)
@TableId(type = IdType.INPUT) // 默认方案
private Long id;
private String name;
private Integer age;
private String email;
}
// 测试插入
@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自动回填
}
@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);
}
创建时间、修改时间!这些个操作一般都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create 、gmt_modify几乎所有的表都要配置上,而且需要自动化!
方式一:数据库级别的修改 (工作中是不允许你修改数据库)
@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);
}
}
注意:处理器指定字段中大写字母不能被识别成-小写字母的格式
在面试过程中,我们经常会被问道乐观锁,悲观锁,其实原理非常简单
乐观锁 OptimisticLockerInnerInterceptor
顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁!如果出现问题就再次更新测试
这里引出 旧version 新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
给数据库中增加version字段
实体类加对应的字段
//乐观锁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);
}
// 测试乐观锁多线程失败 多线程
/*
线程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);
}
@Test
void select() {
User user = userMapper.selectById(1l);
System.out.println(user);
}
@Test
void select() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1,2,3));
for (User user : users) {
System.out.println(user);
}
}
@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);
}
}
分页在网站使用的十分之多!
mybatisplus实现分页
@EnableTransactionManagement
@Configuration
public class mybatisplusconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
}
// 测试分页查询
@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());
}
@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);
}
@Test
void select() {
HashMap<String, Object> map = new HashMap<>();
map.put("name","超哥");
int i = userMapper.deleteByMap(map);
System.out.println(i);
}