SpringBoot2
springboot在版本3以上最低支持jdk17,所以jdk8只能用springboot2.x.x
注意: 导入他人的项目,如果依赖成功加载,却仍显示找不到,可以在 文件—清除缓存,并重启。
概念
SpringBoot 对 spring 的简化,也是其核心功能和优点:
- 起步依赖
- 自动配置
- 辅助功能(内置服务器…)
具体体现在:
- parent
- starter
- 引导类
- 内嵌tomcat
parent
由于依赖的版本之间有一定的固定搭配,所以 SpringBoot 制作了多个技术版本搭配的列表,就叫做 parent。所以 ,使用parent可以帮助开发者进行版本的统一管理。
在pom.xml中继承了一个坐标:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
打开后,可以看到主要有两组信息:
-
第一组是 各式各样的依赖版本号属性;
-
第二组是 各式各样的的依赖坐标信息,其中依赖坐标定义是应用了第一组信息中定义的依赖版本属性值; 且是在<dependencyManagement> 标签中,所以这些定义的依赖还未导入,SpringBoot会在starter中导入一些固定的依赖组合。
<!-- 第一组信息 版本属性值 --> <properties> <activemq.version>5.16.3</activemq.version> <aspectj.version>1.9.7</aspectj.version> ..... ..... </properties> <!-- 第二组信息 依赖坐标 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> ...... ...... </dependencies> </dependencyManagement>
starter
依赖坐标的使用往往有一些固定的组合方式,SpringBoot 定义了starter: starter是指使用某种技术时 依赖的固定搭配格式,也是一种最佳解决方案。 使用starter可以帮助开发者减少依赖配置。
所以,实际开发中如果需要用什么技术,先去找有没有这个技术对应的starter:
- 如果有对应的starter,直接写starter,而且无需指定版本,版本由parent提供;
- 如果没有对应的starter,再手写坐标;
SpringBoot 对于 starter 的名称都是如下格式:
命名规则:spring-boot-starter-技术名称
所有的starter中都会依赖一个starter,叫做spring-boot-starter,它是所有的SpringBoot的starter的基础依赖,里面定义了SpringBoot相关的基础配置。
小结:
- 开发SpringBoot程序需要导入坐标时通常导入对应的starter
- 每个不同的starter根据功能不同,通常包含多个依赖坐标
- 使用starter可以实现快速配置的效果,达到简化配置的目的
引导类
程序运行的入口是 SpringBoot工程创建时自带的有main方法的那个类,运行这个类就可以启动SpringBoot工程的运行。
@SpringBootApplication
public class xxxApplication {
public static void main(String[] args) {
SpringApplication.run(xxxApplication.class, args);
}
}
Spring程序运行的基础是需要创建自己的Spring容器对象(IoC容器)并将所有的对象交给Spring的容器管理,也就是一个一个的Bean。而SpringBoot本身是为了加速Spring程序的开发的,所以创建了这个 引导类,它是所有功能的入口。
内嵌tomcat
在导入的依赖中:
- spring-boot-starter-web
- spring-boot-starter-tomcat
- tomcat-embed-core(tomcat内嵌核心)
- spring-boot-starter-tomcat
就是 tomcat-embed-core 把tomcat功能引入到程序中。
内嵌Tomcat运行原理:Tomcat服务器是一款使用java语言开发的软件,那么tomcat的运行肯定也是以对象的形式运行的,即tomcat可以交给Spring容器管理。
更换内嵌的tomcat:
- tomcat(默认):apache出品,粉丝多,应用面广,负载了若干较重的组件
- jetty:更轻量级,负载性能远不及tomcat
- undertow:负载性能勉强跑赢tomcat
只有加入对应的依赖坐标,并排除tomcat坐标即可:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> </dependencies>
配置文件
SpringBoot将配置信息集中在一个文件 application.properties 中写,不管是服务器的配置,还是数据库的配置,都是写在一起的。
配置的书写有格式和规范,具体的可以参考官方文档(的附录Application Properties):https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
导入的spring-boot-starter-web依赖提供了 配置书写 的提示。
支持的配置文件格式:(一般使用 yml格式)
- properties格式
- yml格式
- yaml格式
yaml只是后缀名和yml不一样,其实是同一种格式,虽然 .yaml 才是YAML官方认定的文件拓展名,但实际上,未认定的 .yml 用的人用多得多。
如果三个文件共存的话,不同的配置都会生效,而相同、冲突的配置类型的 优先级从高到低:.properties > .yml > .yaml
YAML
YAML(YAML Ain’t Markup Language),一种数据序列化格式。
-
具有容易阅读、容易与脚本语言交互、以数据为核心,重数据轻格式的特点。
-
具有严格的语法格式要求:
- 大小写敏感
- 属性层级关系使用多行描述,每行结尾使用冒号":" 结束
- 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
- 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
- #号 表示注释
# 常见的数据书写格式 boolean: TRUE #TRUE,true,True,FALSE,false,False均可 float: 3.14 #6.8523015e+5 #支持科学计数法 int: 123 #0b1010_0111_0100_1010_1110 #支持二进制、八进制、十六进制 null: ~ #使用~表示null string: HelloWorld #字符串可以直接书写 string2: "Hello World" #可以使用双引号包裹特殊字符 date: 2018-02-17 #日期必须使用yyyy-MM-dd格式 datetime: 2018-02-17T15:02:31+08:00 #时间和日期之间使用T连接,最后使用+代表时区 # 此外,还可以表示数组,用减号作为数据开始符号 subject: - Java - 前端 - 大数据 enterprise: name: itcast age: 16 subject: - Java - 前端 - 大数据 likes: [java,前端,大数据] #数组书写缩略格式 #对象数组格式一 users: - name: Tom age: 4 - name: Jerry age: 5 #对象数组格式二 users: - name: Tom age: 4 - name: Jerry age: 5 users2: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ] #对象数组缩略格式
注:
yaml文件对于数字的定义支持多种进制格式,如果是使用字符串,要用引号明确标注。
如: password: 0127 ,这里开头是0,又没有引号,所以会被识别为八进制,转为十进制就是87。
读取数据
读取单个数据:
yaml中保存的单个数据,可以使用Spring中的注解直接读取,使用**@Value** 可以读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
读取全部数据:
SpringBoot提供了一个对象叫 Environment,使用自动装配注解可以将所有的yaml数据封装到这个对象中,然后使用 具体方法getProperties(String),参数填写属性名即可。
@Autowired
private Environment env;
env.getProperty("xxx")
读取对象数据:
由于单一数据读取太麻烦,Environment又封装的太厉害。所以一般都是将一组数据封装成一个对象。
步骤:
- 定义一个对象,并将该对象纳入Spring管控的范围,也就是定义成一个bean;
- 然后使用注解@ConfigurationProperties指定该对象加载哪一组yaml中配置的信息。(需要告知加载的数据前缀)
@Component
@ConfigurationProperties(prefix = "book")
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
book:
id: 12
type: abcd
name: zzc
description: hhhhh
数据的引用:
对于重复出现的数据,可以使用 ${} 进行引用:
center:
dataDir: D:/usr/local/fire/data
tmpDir: D:/usr/local/fire/tmp
logDir: D:/usr/local/fire/log
msgDir: D:/usr/local/fire/msgDir
# 使用引用减少重复书写
baseDir: D:/usr/local/fire
center:
dataDir: ${baseDir}/data
tmpDir: ${baseDir}/tmp
logDir: ${baseDir}/log
msgDir: ${baseDir}/msgDir
注:如果需要解析转义字符,需要用双引号包裹起来作为字符 才能解析。
更多数据配置
@ConfigurationProperties
@ConfigurationProperties除了给自定义的bean加载属性值,也可以为第三方bean加载属性,不过格式会特殊些。
- 在yml中定义要绑定的属性,注意datasource此时全小写
datasource:
driverClassName: com.mysql.jdbc.Driver
-
使用@Bean注解定义第三方bean,使用@ConfigurationProperties注解为第三方bean进行属性绑定,注意前缀是全小写的datasource
@Bean @ConfigurationProperties(prefix = "datasource") public DruidDataSource datasource(){ DruidDataSource ds = new DruidDataSource(); return ds; }
问题:@ConfigurationProperties注解可以为bean进行属性绑定, 所以它可以写在 类和方法上,找起来比较麻烦。
所以有一个注解:@EnableConfigurationProperties,可以标注使用@ConfigurationProperties注解绑定属性的bean是哪些。使用如下:
-
在配置类上开启@EnableConfigurationProperties注解,并标注要使用@ConfigurationProperties注解绑定属性的类
@SpringBootApplication @EnableConfigurationProperties(ServerConfig.class) public class Springboot13ConfigurationApplication { }
-
在对应的类上直接使用@ConfigurationProperties进行属性绑定
@Data @ConfigurationProperties(prefix = "servers") public class ServerConfig { private String ipAddress; private int port; private long timeout; }
注:这里不需要@Component 来标注为bean, 因为使用@EnableConfigurationProperties注解时,spring会默认将其标注的类定义为bean,因此无需再次声明@Component注解了。 (一起用会标注重复的bean,会报错)
-
在yml中定义要绑定的属性:
servers: ip-address: 192.168.0.1 port: 2345 timeout: 3000
如果使用@ConfigurationProperties注解时,出现一个标红的坐标提示信息,可以添加一个坐标以解决:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
宽松绑定
进行数据绑定时, yml文件和@ConfigurationProperties(prefix = “xx”)的 前缀必须是小写 的,否则会出错。
而前缀所管辖的数据则非常宽松,也就是,配置文件中的命名格式与变量名的命名格式可以进行格式上的最大化兼容。只要字母对的上,不论大小写不一致还是加了 “_” 或 "-“ 都能正确加载。
springboot推荐使用烤肉串模式/中划线模式,如 ip-address ,data-source。
注:宽松绑定仅针对springboot中@ConfigurationProperties注解进行属性绑定时有效,对@Value是无效的
常用计量单位绑定
对于yml文件中的需要规定单位的配置值,如时间,存储大小。springboot利用了JDK8中提供的用来表示计量单位的数据类型 来绑定计量单位。
-
Duration:表示时间间隔,可以通过 @DurationUnit 注解描述时间单位,例如上例中描述的单位为小时(ChronoUnit.HOURS)
常用单位:
DAYS,SECONDS,MINUTES,HALF_DAYS,CENTURIES,
DECADES,ERAS,FOREVER,HOURS,MICROS,MILLENNIA,
MILLIS,MONTHS,NANOS,WEEKS,YEARS
-
DataSize:表示存储空间,可以通过 @DataSizeUnit 注解描述存储空间单位,例如上例中描述的单位为MB(DataUnit.MEGABYTES)
常用单位:MEGABYTES,BYTES,GIGABYTES,KILOBYTES,TERABYTES
@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
@DurationUnit(ChronoUnit.HOURS)
private Duration serverTimeOut;
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize dataSize;
}
校验
对于配置中的数据和代码中的定义的数据,可能存在数据类型不匹配的问题,这样会无法正常绑定。所以需要校验功能。
在JAVAEE的JSR303规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架。
此处使用Hibernate提供的校验框架来作为实现进行数据校验。
-
开启校验框架
<!--1.导入JSR303规范--> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <!--使用hibernate框架提供的校验器做实现--> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>
-
在需要开启校验功能的类上使用注解@Validated开启校验功能
@Component @Data @ConfigurationProperties(prefix = "servers") @Validated //开启对当前bean的属性注入校验 public class ServerConfig { }
-
对具体的字段设置校验规则
@Component @Data @ConfigurationProperties(prefix = "servers") @Validated //开启对当前bean的属性注入校验 public class ServerConfig { //设置具体的规则 @Max(value = 8888,message = "最大值不能超过8888") @Min(value = 202,message = "最小值不能低于202") private int port; }
通过设置数据格式校验,就可以有效避免非法数据加载。
DTO部分
DTO,即数据传输对象,用于表现层和应用层之间的数据交互。
简单来说Model面向业务,我们是通过业务来定义Model的。而DTO是面向界面UI,是通过UI的需求来定义的。 通过DTO我们实现了表现层与Model之间的解耦。
例子:
-
表现层一致性处理: Result 结果类
一般后端传给前端的数据,都应该封装成一个同一的结果类,所以需要定义一个dto,统一格式,如:
@Data @NoArgsConstructor @AllArgsConstructor public class Result { private Boolean success; //业务是否成功 private String errorMsg; //出错时要传递的消息 private Object data; //业务数据 private Long total; // public static Result ok(){ return new Result(true, null, null, null); } public static Result ok(Object data){ return new Result(true, null, data, null); } public static Result ok(List<?> data, Long total){ return new Result(true, null, data, total); } public static Result fail(String errorMsg){ return new Result(false, errorMsg, null, null); } }
-
实体类与要传给前端时,有些变量不一致时,也要定义一个dto。
如:实体类User中有密码等隐私字段,而传出去的数据不能包含这些信息,所以可以定义一个UserDTO,只定义需要的变量即可。
@Data public class UserDTO { private Long id; private String nickName; private String icon; }
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("tb_user") public class User implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(value = "id", type = IdType.AUTO) private Long id; /** * 手机号码 */ private String phone; /** * 密码,加密存储 */ private String password; /** * 昵称,默认是随机字符 */ private String nickName; /** * 用户头像 */ private String icon = ""; /** * 创建时间 */ private LocalDateTime createTime; /** * 更新时间 */ private LocalDateTime updateTime; }
SpringBoot整合
SpringBoot整合其他技术,都是先导入对应的starter或坐标,再去使用对应技术。
整合JUnit
JUnit的依赖默认导入了。
使用注解@SpringBootTest替换了原来的两个注解。如果要手动加载引导类,就写上属性 classes = 引导类.class
@SpringBootTest
class Tests {
//注入你要测试的对象
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
//执行要测试的对象对应的方法
bookDao.save();
}
}
整合Mybatis
原来的Spring整合Mybatis的步骤需要有:
- 导入坐标
- Spring核心配置:SpringConfig
- MyBatis要交给Spring接管的bean:MyBatisConfig
- 数据源对应的bean
- 数据库连接信息:jdbc.properties
而使用 SpringBoot 整合 Mybatis,只需要:
-
创建模块时勾选对应技术:Mybatis和Mysql
或者手工导入:
<dependencies> <!--1.导入对应的starter--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>
-
配置数据源信息:
#2.配置相关信息 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db username: root password: zzc
到此,就配置完毕了。之后只需书写 实体类和映射接口(Dao)即可:
//实体类
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
//映射接口
@Mapper
public interface BookDao {
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
}
//测试类
@SpringBootTest
class Springboot05MybatisApplicationTests {
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
System.out.println(bookDao.getById(1));
}
整合MybatisPlus
-
导入对应的starter:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency>
第三方提供的starter,命名为:**-spring-boot-starter 或者 **-boot-starter
-
配置数据源信息
#2.配置相关信息 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db username: root password: root
同样两步配置完毕,之后写MybatisPlus的程序:
//映射接口
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
//测试类
.....
整合Druid
-
导入对应坐标
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> </dependencies>
-
修改数据源:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: root type: com.alibaba.druid.pool.DruidDataSource
在数据源配置中的type属性,用于指定数据源类型(默认指定的数据源时HiKari)
如果数据源需要进行个性化的配置,就需要导入对应数据源的starter,如导入Druid的:
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> </dependencies>
修改配置:
spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: root
运维事项
工程打包
- 使用maven的package进行打包,会打包至target目录下, 后缀为 .jar
- 使用 "java -jar 打包的文件名"命令运行工程即可。
注:jar支持命令启动需要依赖maven插件,需要确定有导入以下插件。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
临时属性
- 使用jar命令启动SpringBoot工程时可以使用临时属性替换配置文件中的属性
- 临时属性添加方式:java –jar 工程名.jar –-属性名=值
- 多个临时属性之间使用空格分隔
- 临时属性必须是当前boot工程支持的属性,否则设置无效
java -jar 项目打包文件.jar --server.port=8080 --spring.datasource.druid.password=123
在开发环境jdea中测试临时属性:在运行/调试配置中的“程序参数”进行输入。
在“程序参数”中输入的数据,其实会保存到启动程序的参数args中:
@SpringBootApplication
public class XxxApplication {
public static void main(String[] args) {
SpringApplication.run(XxxApplication.class, args); //args参数可以不传,相当于断掉外部临时参数的入口,使得临时参数无法生效
}
}
配置文件
配置文件4级分类: 满足不同阶段对配置的设置需求
-
SpringBoot中4级配置文件
1级: file :config/application.yml 【级别最高】
(file是指打包文件.jar所在的目录)
2级: file :application.yml
3级:classpath:config/application.yml
(classpath是源根,即工程的resources目录)
4级:classpath:application.yml 【级别最低】
-
作用:
-
1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控。2级用于运维人员配置涉密的线上环境。
-
3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控,4级是程序员开发时用的。
-
自定义配置文件:
在运行/调试配置中的“程序参数”中输入自定义的配置文件:
--spring.config.name=文件名
--spring.config.location=classpath:/文件名.yml
多配置文件常用于将配置进行分类,进行独立管理,或将将可选配置单独制作便于上线更新维护:
--spring.config.location=classpath:/文件名.yml,classpath:/文件名.properties
- 单服务器项目:使用自定义配置文件需求较低
- 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
- 基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息
多环境开发配置
多环境开发:
1.单文件:
# 应用哪个环境, 以及公共的配置
spring:
profiles:
active: pro
# 使用 --- 区分不同的环境, spring.profiles配置环境名
---
# 生产环境
spring:
profiles: pro
server:
port: 80
---
# 开发环境
spring:
profiles: dev
server:
port: 81
---
# 测试环境
spring:
profiles: test
server:
port: 82
2.多文件:
把多个环境的配置放在一个文件里,安全性不好,可以采用多文件的配置方式:
- 主启动文件 application.yml
# 应用哪个环境, 以及公共的配置
spring:
profiles:
active: pro
环境分类文件的命名为: application-环境名.yml
-
环境分类文件 application-pro.yml
server: port: 80
-
环境分类文件 application-dev.yml
server: port: 81
-
环境分类文件 application-test.yml
server: port: 82
多环境分组管理:
根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下
- application-devDB.yml
- application-devRedis.yml
- application-devMVC.yml
主启动文件application.yml中使用include属性进行指定:
spring:
profiles:
active: dev
include: devDB,devRedis,devMVC
从Spring2.4版开始使用group属性替代include属性,降低了配置书写量 —— 使用group属性定义多种主环境与子环境的包含关系
spring:
profiles:
active: dev
group:
"dev": devDB,devRedis,devMVC
"pro": proDB,proRedis,proMVC
"test": testDB,testRedis,testMVC
Maven与SpringBoot多环境兼容:
当Maven与SpringBoot同时对多环境进行控制时,以Mavn为主, SpringBoot使用@…@占位符读取Maven对应的配置属性值:
-
Maven中设置多环境属性(pom.xml):
<profiles> <profile> <id>dev_env</id> <properties> <profile.active>dev</profile.active> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>pro_env</id> <properties> <profile.active>pro</profile.active> </properties> </profile> <profile> <id>test_env</id> <properties> <profile.active>test</profile.active> </properties> </profile> </profiles
-
Spring中引用Maven属性:
spring: profiles: active: @profile.active@
-
执行Maven打包,可在生成的boot打包.jar文件中查看。
日志
日志作用:
- 编程期调试代码
- 运营期记录信息:
- 记录日常运营重要信息(峰值流量、平均响应时长……)
- 记录应用报错信息(错误堆栈)
- 记录运维过程数据(扩容、宕机、报警……)
使用:
1.导入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2.yml配置文件中可以设置日志级别:debug,info,warn,error
# 开启debug模式,输出调试信息,常用于检查系统运行状况
debug: true
# 设置日志级别,root表示根节点,即整体应用日志级别
logging:
# 设置日志组
group:
# 自定义组名,设置当前组中所包含的包
ebank: com.zzc.controller,com.zzc.dao
level:
root: warn
# 为对应组设置日志级别
ebank: debug
# 为对包设置日志级别
com.zzc.controller: debug
3.在对应类上添加注解:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
public void test(){
log.debug("debug ...");
log.info("info ...");
log.warn("warn ...");
log.error("error ...");
}
}
或者:
private static final Logger log = LoggerFactory.getLogger(UserController.class);
log.debug("debug ...");
log.info("info ...");
log.warn("warn ...");
log.error("error ...");
日志输出格式控制:
logging:
pattern:
console: "%d %clr(%p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"
默认的是: 时间,级别,PID,所属线程,所属类/接口名,日志信息
设置记录日志的文件:
logging:
# 单一日志文件,日志都记在一个文件,会导致文件过大
file:
name: server.log
# 滚动日志文件,超过指定大小就再新建一个日志文件
logback:
rollingpolicy:
# 指定的文件大小
max-file-size: 3KB
# 文件名
file-name-pattern: server.%d{yyyy-MM-dd}.%i.log
开发使用
热部署
热部署,就是修改程序后,服务器只修改更新后的程序,不用重启服务器。
springboot项目设置热部署:
部署原理:
一个springboot项目在运行时实际上是分两个过程进行的,根据加载的东西不同,划分成base类加载器与restart类加载器。
- base类加载器:用来加载jar包中的类,jar包中的类和配置文件由于不会发生变化,因此不管加载多少次,加载的内容不会发生变化
- restart类加载器:用来加载开发者自己开发的类、配置文件、页面等信息,这一类文件受开发者影响
当springboot项目启动时,base类加载器执行,加载jar包中的信息后,restart类加载器执行,加载开发者制作的内容。当执行构建项目后,由于jar中的信息不会变化,因此base类加载器无需再次执行,所以仅仅运行restart类加载即可,也就是将开发者自己制作的内容重新加载就行了,这就完成了一次热部署的过程。
也可以说热部署的过程实际上是重新加载restart类加载器中的信息。
-
手动启动热部署:
-
导入对应坐标:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
-
手动构建项目: bulid——Build Project,或 CTRL+F9
-
-
自动启动热部署:
- 打开 File —— settings —— Build,Execution,Deployment —— Compiler —— 勾选【Build project automatically】,意思是自动构建项目
- 使用快捷键 Ctrl+Alt+Shift+/ ,打开维护面板,选择第1项【Registry…】,搜索compiler,勾选。这样就完成了。
idea设置当idea工具失去焦点5秒后进行热部署。就是你从idea工具中切换到其他工具时进行热部署,比如改完程序需要到浏览器上去调试,这个时候idea就自动进行热部署操作。
设置热部署监控的文件范围(有些文档文件的修改不需要热部署):
—— 开发者工具中有一组配置,当满足了配置中的条件后,才会启动热部署,配置中默认不参与热部署的目录信息如下:
- /META-INF/maven
- /META-INF/resources
- /resources
- /static
- /public
- /templates
以上目录中的文件如果发生变化,是不参与热部署的。如果想修改配置,可以通过application.yml文件进行设定哪些文件不参与热部署操作:
spring:
devtools:
restart:
# 设置不参与热部署的文件或文件夹
exclude: static/**,public/**,config/application.yml
关闭热部署:
线上环境运行时是不可能使用热部署功能的,所以需要强制关闭此功能,通过配置可以关闭此功能(关闭了监视代码变动的功能):
spring:
devtools:
restart:
enabled: false
或者(优先级比配置文件高,不容易被其他配置文件覆盖):
@SpringBootApplication
public class XxxApplication {
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled","false");
SpringApplication.run(XxxApplication.class);
}
}
测试
加载测试用属性
测试过程需要模拟一些线上情况,或者模拟一些特殊情况。如果当前环境按照线上环境已经设定好了,例如是下面的配置:
env:
maxMemory: 32GB
minMemory: 16GB
但是如果想测试对应的兼容性,需要测试如下配置:
env:
maxMemory: 16GB
minMemory: 8GB
但测试时不能随意修改源码,所以需要在测试环境中创建一组临时属性,去覆盖源码中设定的属性。
-
临时属性
在测试用例中,springboot可以通过注解@SpringBootTest添加属性来模拟临时属性:
//properties属性可以为当前测试用例添加临时的属性配置 @SpringBootTest(properties = {"test.prop=testValue1"}) public class PropertiesAndArgsTest { @Value("${test.prop}") private String msg; @Test void testProperties(){ System.out.println(msg); } }
-
临时参数
springboot可以通过命令行参数输入配置信息,测试用例通过@SpringBootTest(args={}) 也可以:
//args属性可以为当前测试用例添加临时的命令行参数 @SpringBootTest(args={"--test.prop=testValue2"}) public class PropertiesAndArgsTest { @Value("${test.prop}") private String msg; @Test void testProperties(){ System.out.println(msg); } }
加载测试用bean
测试时,可能需要独立的bean,专门应用于测试环境:
-
在测试包test中创建专用的测试环境配置类
@Configuration public class MsgConfig { @Bean public String msg(){ return "bean msg"; } }
-
在启动测试环境处,导入测试环境专用的配置类,使用@Import注解即可实现
@SpringBootTest @Import({MsgConfig.class}) public class ConfigurationTest { @Autowired private String msg; @Test void testConfiguration(){ System.out.println(msg); } }
Web环境模拟测试
在测试中对表现层功能进行测试需要一个基础和一个功能。
- 一个基础是:运行测试程序时,必须启动web环境,不然没法测试web功能。
- 一个功能是:必须在测试程序中具备发送web请求的能力,不然无法实现web功能的测试。
测试类中启动web环境:
在测试类上方的@SpringBootTest注解,有一个属性叫做webEnvironment。通过该属性就可以设置在测试用例中启动web环境:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {
}
springboot提供了4中设置值:
- MOCK:根据当前设置确认是否启动web环境,例如使用了Servlet的API就启动web环境,属于适配性的配置
- DEFINED_PORT:使用自定义的端口作为web服务器端口
- RANDOM_PORT:使用随机端口作为web服务器端口
- NONE:不启动web环境
通过上述配置,启动测试程序时就可以正常启用web环境,建议测试时使用RANDOM_PORT,避免代码中因为写死设定引发线上功能打包测试时由于端口冲突导致意外现象的出现。
测试类中发送请求:
-
在测试类中通过注解@AutoConfigureMockMvc 开启web虚拟调用功能:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //开启虚拟MVC调用 @AutoConfigureMockMvc public class WebTest { }
-
定义发起虚拟调用的对象MockMVC,通过自动装配的形式初始化对象:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //开启虚拟MVC调用 @AutoConfigureMockMvc public class WebTest { // @Autowired // private MockMvc mvc; @Test void testWeb(@Autowired MockMvc mvc) { } }
-
创建一个虚拟请求对象,封装请求的路径,并使用MockMVC对象发送对应请求:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //开启虚拟MVC调用 @AutoConfigureMockMvc public class WebTest { @Test void testWeb(@Autowired MockMvc mvc) throws Exception { //http://localhost:8080/books //创建虚拟请求,当前访问/books MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); //执行对应的请求 mvc.perform(builder); } }
-
发完请求会得到一个响应对象,测试该响应对象的各种信息是否符合要求:
@Test void testStatus(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); ResultActions action = mvc.perform(builder); //设定预期值 与真实值进行比较,成功测试通过,失败测试失败 //对比状态码: //定义本次调用的预期值 StatusResultMatchers status = MockMvcResultMatchers.status(); //预计本次调用时成功的:状态200 ResultMatcher ok = status.isOk(); //添加预计值到本次调用过程中进行匹配 action.andExpect(ok); //对比响应头信息 HeaderResultMatchers header = MockMvcResultMatchers.header(); ResultMatcher contentType = header.string("Content-Type", "application/json"); action.andExpect(contentType); //对比响应体内容(非json格式) ContentResultMatchers content = MockMvcResultMatchers.content(); ResultMatcher result = content.string("springboot2"); action.andExpect(result); //对比响应体内容(json格式) ContentResultMatchers content = MockMvcResultMatchers.content(); ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot2\",\"type\":\"springboot\"}"); action.andExpect(result); }
数据层测试回滚
打包的阶段的test生命周期属于必须被运行的生命周期,如果跳过会给系统带来极高的安全隐患,所以测试用例必须执行。
不过为了避免测试过程对数据库产生影响,所以需要让测试程序不提交事务:
- 在测试用例中添加注解@Transactional 即可实现当前测试用例的事务不提交。
@SpringBootTest
@Transactional
@Rollback(true)
public class DaoTest {
@Autowired
private BookService bookService;
@Test
void testSave(){
Book book = new Book();
book.setName("springboot3");
book.setType("springboot3");
book.setDescription("springboot3");
bookService.save(book);
}
}
如果想提交事务,可以修改为:@Rollback(false),即可正常提交事务。
随机测试数据
对于测试用例的数据固定书写肯定是不合理的,springboot提供了在配置中使用随机值的机制,确保每次运行程序加载的数据都是随机的。具体如下:
testcase:
book:
id: ${random.int}
id2: ${random.int(10)}
type: ${random.int!5,10!}
name: ${random.value}
uuid: ${random.uuid}
publishTime: ${random.long}
当前配置就可以在每次运行程序时创建一组随机数据,避免每次运行时数据都是固定值的尴尬现象发生,有助于测试功能的进行。数据的加载按照之前加载数据的形式,使用@ConfigurationProperties注解即可
@Component
@Data
@ConfigurationProperties(prefix = "testcase.book")
public class BookCase {
private int id;
private int id2;
private int type;
private String name;
private String uuid;
private long publishTime;
}
对于随机值的产生,还有一些小的限定规则,比如产生的数值性数据可以设置范围等:
testcase:
book:
id: ${random.int} #随机数
id2: ${random.int(10)} # 10以内的随机数
type: ${random.int!5,10!} #5到10的随机数
初始化阶段
在 Spring Boot 应用程序中,ApplicationRunner
,CommandLineRunner
和 ApplicationListener
都可用于在应用程序启动时执行一些特定操作。
以下是它们之间的主要区别:
-
ApplicationRunner: ApplicationRunner 是一个接口,用于在 Spring Boot 应用程序启动时运行特定代码。当实现 ApplicationRunner 接口时,需要重写
run
方法。该方法接收一个ApplicationArguments
类型的参数,可以用于处理传递给应用程序的命令行参数。public class MyAppRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { System.out.println("ApplicationRunner running..."); } }
-
CommandLineRunner: CommandLineRunner 也是一个接口,与 ApplicationRunner 类似,用于在 Spring Boot 应用程序启动时运行特定代码。实现 CommandLineRunner 接口时,也需要重写
run
方法。不同的是,该方法接收一个字符串数组(String[]
)作为参数,用于处理传递给应用程序的命令行参数。public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) { System.out.println("CommandLineRunner running..."); } }
-
ApplicationListener: ApplicationListener 是一个泛型接口,用于在 Spring Boot 应用程序中监听特定类型的事件。实现 ApplicationListener 接口时,需要指定监听的事件类型,并重写
onApplicationEvent
方法。在此方法中处理事件。与 ApplicationRunner 和 CommandLineRunner 不同,ApplicationListener 可用于监听各种事件,而不仅仅是应用程序启动事件。例如,要在应用程序启动时执行特定操作,可以实现
ApplicationListener<ApplicationReadyEvent>
:public class MyAppListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent event) { // 只有当前ApplicationContext是根上下文时,此时程序处于根上下文初始化阶段 if (event.getApplicationContext().getParent() == null) { ...... } } }
总结:
- ApplicationRunner 和 CommandLineRunner 都用于在 Spring Boot 应用程序启动时执行特定代码,但它们处理命令行参数的方式不同。ApplicationRunner 使用 ApplicationArguments 对象,而 CommandLineRunner 使用字符串数组。
- ApplicationListener 用于监听应用程序中的特定类型事件,不仅限于启动事件。可以通过实现 ApplicationListener 并指定相应事件类型来处理不同的事件。
数据层解决方案
自定义序列化
-
构建要进行序列化的实体类User:
会用到的常见json注解:
- @JsonIgnoreProperties:
此注解是类注解,作用是在json序列化时将Java bean中的某些属性忽略掉,序列化和反序列化都受影响。 - @JsonIgnore:
此注解用于属性或者方法上(最好是属性上),作用和上面的@JsonIgnoreProperties一样。 - @JsonFormat:
此注解用于属性或者方法上(最好是属性上),可以方便的把Date类型直接转化为我们想要的模式,比如@JsonFormat(pattern = “yyyy-MM-dd HH-mm-ss”) - @JsonSerialize:
此注解用于属性或者getter方法上,用于在序列化时嵌入我们自定义的序列化器,比如序列化一个double时在其后面限制两位小数点。 - @JsonDeserializ:
此注解用于属性或者setter方法上,用于在反序列化时嵌入我们自定义的反序列化器,比如反序列化一个Date类型的时间字符串。 - @JsonCreator与@JsonProperty:
该注解的作用就是指定反序列化时替代无参构造函数,构造方法的参数前面需要加上@JsonProperty注解。
@Data @ToString @JsonIgnoreProperties(value = {"word"}) public class User { /** * 注意:在进行JSON序列化和反序列化时,要么提供一个无参的构造方法,要么在其他构造方法上添加@JsonCreator注解. */ private String name; private int age; private boolean sex; private Date birthday; private String word; private double salary; // @JsonCreator指定反序列化时用于替代无参构造函数, 参数前要加上@JsonProperty @JsonCreator public User(@JsonProperty("name") String name, @JsonProperty("age") int age, @JsonProperty("sex") boolean sex, @JsonProperty("birthday") Date birthday, @JsonProperty("word") String word, @JsonProperty("salary") double salary) { super(); this.name = name; this.age = age; this.sex = sex; this.birthday = birthday; this.word = word; this.salary = salary; } /** * 反序列化一个固定格式的Date */ @JsonDeserialize(using = CustomeJackson.MyDeserializer.class) public void setBirthday(Date birthday) { this.birthday = birthday; } /** * 序列化指定格式的double格式 */ @JsonSerialize(using = CustomeJackson.MySerializer.class) public double getSalary() { return salary; } }
- @JsonIgnoreProperties:
-
编写自己的JsonSerializer和JsonDeserializer类,来进行自定义的序列化和反序列操作。并使用**@JsonComponent注解**将其注册为spring beans(原来是用是通过Module方式注册到Jackson中)。
@JsonComponent public class CustomeJackson { /** * 自定义序列化器,格式化数值 */ public static class MySerializer extends JsonSerializer<Double> { private DecimalFormat df = new DecimalFormat("##.00"); /** * 序列化操作,继承JsonSerializer,重写Serialize函数 */ @Override public void serialize( Double value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(df.format(value)); } } /** * 自定义反序列化器,格式化时间 */ public static class MyDeserializer extends JsonDeserializer<Date> { private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); @Override public Date deserialize( JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { Date date = null; try { date = sdf.parse(jsonParser.getText()); } catch (ParseException e) { e.printStackTrace(); } return date; } } }
-
直接编写controller即可,会自动进行序列化操作
@RestController public class UserController { /** * 将对象转为json字符串-->序列化 */ @GetMapping("/user/{salary}") public User home(@PathVariable("salary") Long salary) { return new User("一一哥", 30, true, new Date(), "程序员", salary); } /** * 将一个json转化为对象-->反序列化 */ @RequestMapping(value = "user") public String getValue(@RequestBody User user) { log.warn("user=" + user.toString()); return user.toString(); } }
MybatisPlus
概念
国人开发的Mybatis的便捷版。
使用步骤
-
导入相关的starter:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
-
配置数据源:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC username: root password: zzc
-
如果数据源需要进行个性化的配置,就需要导入对应数据源的starter,如导入Druid的:
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> </dependencies>
修改配置:
spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: root
-
-
创建数据层的 Dao/Mapper 接口
接口继承BaseMapper接口 —— BaseMapper接口中提供了许多常用方法,我们只需要从容器中获取Mapper就可以了,不用自己去编写sql语句。
扫描:可以直接在接口上加@Mapper,或者在启动类上设置扫描@MapperScan(“xxx”)
@Mapper public interface BookDao extends BaseMapper<Book> { }
-
测试
常用设置
表的映射规则
默认情况下,MP操作的表名就是实体类的类名,但如果表名与类名不一致就需要设置映射规则。
单独设置: 在实体类上加上 @TableName
@TableName("tbl_book")
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
全局设置: 一般一个项目的表名的前缀有统一风格,可以通过 .yml 配置文件配置
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_ #设置表名通用前缀
主键生成策略
默认情况下,使用MP插入数据时,主键生成策略是基于雪花算法的自增id。
如果需要使用别的策略,可以:
单独设置: 在代表主键的字段上加上 @TableId,使用其type属性指定主键生成策略。
@Data
public class Book {
//设置主键自增长
@TableId(type = IdType.AUTO)
private Integer id;
...
}
全局设置:
mybatis-plus:
global-config:
db-config:
id-type: auto #设置主键id字段的生成策略 为参照数据库设定的策略,默认是使用 基于雪花算法的自增id
字段映射
默认MP会根据实体类的属性名去映射表的列名。
如果列名和实体类的属性不一致,可以用 @TableField 设置映射关系。
@TableField("address")
private String addressStr;
驼峰映射
默认情况下MP会开启字段名列名的驼峰映射,即将驼峰命名的属性翻译成下划线命名,如:userName会翻译成 user_name
sql执行日志
配置sql日志输出,即打印对应的sql执行语句:
mybatis-plus:
configuration:
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
数据层使用
基本方法
包名为dao或mapper, 数据层接口需要继承BaseMapper,BaseMapper提供常见的sql方法。
- select() —— 查询
- selectBatchIds(list) 批量查询
- insert() —— 插入数据
- delete() —— 删除数据
- deleteBatchIds(list) 批量删除
- update() —— 更新数据
条件构造器Wrapper
wrapper是一个接口,用于更方便的构造条件。
继承体系:
- wrapper
- AbstractWrapper
- QueryWrapper
- UpdateWrapper
- AbstractWrapper
AbstractWrapper
提供了许多构造where条件的方法;
QueryWrapper
额外提供了针对Select语法的select
方法;
UpdateWrapper
额外提供了用于针对set语法的set
方法;
提供的方法中,可以加上一个Boolean参数,表示是否要加上当前的条件;因为有些时候传入的变量可能是null,而查询时会直接当作字符串处理,所以可以加上 变量 != null
、Strings.isNotEmpty(变量)
等等, 来判断。
@Test
public void test(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.like(Strings.isNotEmpty(name), User::getName, name);
userMapper.selectList(wrapper);
}
AbstractWrapper
完整的AbstractWrapper方法参照官方文档:条件构造器 | MyBatis-Plus (baomidou.com)
常用AbstractWrapper方法:
eq:equals,等于
gt:greater than ,大于 >
ge:greater than or equals,大于等于≥
lt:less than,小于<
le:less than or equals,小于等于≤
between:相当于SQL中的BETWEEN
like:模糊匹配。like(“name”,“黄”),相当于SQL的name like ‘%黄%’
likeRight:模糊匹配右半边。likeRight(“name”,“黄”),相当于SQL的name like ‘黄%’
likeLeft:模糊匹配左半边。likeLeft(“name”,“黄”),相当于SQL的name like ‘%黄’
notLike:notLike(“name”,“黄”),相当于SQL的name not like ‘%黄%’
isNull
isNotNull
and:SQL连接符AND
or:SQL连接符OR
in: in(“age",{1,2,3})相当于 age in(1,2,3)
groupBy: groupBy(“id”,“name”)相当于 group by id,name
orderByAsc :orderByAsc(“id”,“name”)相当于 order by id ASC,name ASC
orderByDesc :orderByDesc (“id”,“name”)相当于 order by id DESC,name DESC
例1:
SQL语句如下:
SELECT
id,user_name,PASSWORD,NAME,age,address
FROM
`USER`
WHERE
age > 18 AND address = '狐山'
用Wrapper写法如下:
@Test
public void testWrapper01(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.gt("age",18);
wrapper.eq("address","狐山");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
例2:
SQL语句如下:
SELECT
id,user_name,`PASSWORD`,NAME,age,address
FROM
`USER`
WHERE
id IN(1,2,3) AND
age BETWEEN 12 AND 29 AND
address LIKE '%山%'
用Wrapper写法如下:
@Test
public void testWrapper02(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.in("id",1,2,3);
wrapper.between("age",12,29);
wrapper.like("address","山");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
例3:
SQL语句如下:
SELECT
id,user_name,`PASSWORD`,NAME,age,address
FROM
USER
WHERE
id IN(1,2,3) AND
age > 10
ORDER BY
age DESC
用Wrapper写法如下:
@Test
public void testWrapper03(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id",1,2,3);
queryWrapper.gt("age",10);
queryWrapper.orderByDesc("age");
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
QueryWrapper
QueryWrapper的 select() 可以设置 要/不要查询的列名:
-
select(String s… )—— 形参为要查询的列名
-
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) —— 第一个参数为实体类的class字节码对象,第二个参数是Predicate类型,用于过滤要查询的字段(主键除外)
// sql语句: SELECT id,user_name FROM USER 查user_name,而id作为主键默认会查 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.select(User.class, new Predicate<TableFieldInfo>() { @Override public boolean test(TableFieldInfo tableFieldInfo) { //返回true为要查询,false为不查询 return "user_name".equals(tableFieldInfo.getColumn()); } }); List<User> users = userMapper.selectList(queryWrapper);
-
select(Predicate<TableFieldInfo> predicate)
// sql语句: SELECT id,user_name,PASSWORD,NAME,age FROM USER 即不查address字段,其他都查 QueryWrapper<User> queryWrapper = new QueryWrapper<>(new User()); queryWrapper.select(new Predicate<TableFieldInfo>() { @Override public boolean test(TableFieldInfo tableFieldInfo) { return !"address".equals(tableFieldInfo.getColumn()); } }); List<User> users = userMapper.selectList(queryWrapper);
UpdateWrapper
UpdateWrapper的set方法来设置要更新的列及其值,不用传入一个类,同时这种方式也可以使用Wrapper去指定更复杂的更新条件。
//UPDATE USER
// SET age = 99
// where id > 1
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.gt("id",1);
updateWrapper.set("age",99);
userMapper.update(null,updateWrapper);
Lambda条件构造器
MP提供了一个Lambda条件构造器可以让我们直接以实体类的方法引用的形式来指定列名。这样就可以避免在条件构造中 写错列名 了。
QueryWrapper<User> queryWrapper = new QueryWrapper();
//queryWrapper.gt("age",18);
//queryWrapper.eq("address","狐山");
queryWrapper.gt(User::getAge,18);
queryWrapper.eq(User::getAddress,"狐山");
自定义SQL
虽然MP为我们提供了很多常用的方法,并且也提供了条件构造器。但是如果真的遇到了复制的SQL时,我们还是需要自己去定义方法,自己去写对应的SQL,这样SQL也更有利于后期维护。
因为MP是对mybatis做了增强,所以还是支持之前Mybatis的方式去自定义方法。
同时也支持在使用Mybatis的自定义方法时使用MP的条件构造器帮助我们进行条件构造。
Mybatis方式
1、定义方法
在Mapper接口中定义方法
public interface UserMapper extends BaseMapper<User> {
User findMyUser(Long id);
}
2、创建xml
先配置xml文件的存放目录
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
创建对应的xml映射文件
3、在xml映射文件中编写SQL
创建对应的标签,编写对应的SQL语句
<?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.sangeng.mapper.UserMapper">
<select id="findMyUser" resultType="com.sangeng.domian.User">
select * from user where id = #{id}
</select>
</mapper>
Mybatis方式结合条件构造器
我们在使用上述方式自定义方法时。如果也希望我们的自定义方法能像MP自带方法一样使用条件构造器来进行条件构造的话只需要使用如下方式即可。
①方法定义中添加Warpper类型的参数
添加Warpper类型的参数,并且要注意给其指定参数名。
public interface UserMapper extends BaseMapper<User> {
User findMyUserByWrapper(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
}
②在SQL语句中获取Warpper拼接的SQL片段进行拼接。
<select id="findMyUserByWrapper" resultType="com.sangeng.domian.User">
select * from user ${ew.customSqlSegment}
</select>
注意:不能使用#{}应该用${}
分页查询
- 配置分页查询拦截器
@Configuration
public class PageConfig {
/**
* 3.4.0之前的版本
* @return
*/
/* @Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}*/
/**
* 3.4.0之后版本
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
-
接口:IPage, 实现类:Page
//分页功能 @Test void testGetPage(){ IPage page = new Page(2,5); //第2页,每页5条 bookDao.selectPage(page, null); //第二个参数为wrapper page.getCurrent(); //当前页码值 page.getSize(); //每页显示数 page.getTotal(); //数据总量 page.getPages(); //总页数 page.getRecords(); //详细数据 }
业务层Service
包名一般为service,也叫service层。
Service接口定义与数据层接口定义的差别:
- 业务层的接口名称一般是业务名,如登录业务就叫 login(username, passwd)
- 数据层的接口名称,一般要写明用什么查什么,如登录业务中要调用的查询用户信息功能,叫 selectByUserNameAndPassword(username, passwd)
MP提供了service层的实现,只需要编写一个接口,继承IService
,并创建一个接口实现类继承ServiceImpl
,即可使用。
相比于Mapper接口,Service层主要是支持了更多批量操作的方法。
如:
-
之前的写法:
public interface UserService { List<User> list(); }
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public List<User> list() { return userMapper.selectList(null); } }
-
现在的写法:
public interface UserService extends IService<User> { }
@Service public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService { }
需要其他的自定义方法的话,直接加就行。
更多功能
代码生成器
MP提供了一个代码生成器,可以让我们一键生成实体类,Mapper接口,Service,Controller等全套代码 。使用方式如下
- 添加依赖
<!--mybatisplus代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
- 生成
修改相应配置后执行以下代码即可
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.junit.jupiter.api.Test;
public class GeneratorTest {
@Test
public void generate() {
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig config = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
// 设置输出到的目录
config.setOutputDir(projectPath + "/src/main/java");
config.setAuthor("zzc");
// 生成结束后是否打开文件夹
config.setOpen(false);
// 全局配置添加到 generator 上
generator.setGlobalConfig(config);
// 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("zzc");
// 数据源配置添加到 generator
generator.setDataSource(dataSourceConfig);
// 包配置, 生成的代码放在哪个包下
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.zzc");
// 包配置添加到 generator
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategyConfig = new StrategyConfig();
// 下划线驼峰命名转换
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
// 开启lombok
strategyConfig.setEntityLombokModel(true);
// 开启RestController
strategyConfig.setRestControllerStyle(true);
generator.setStrategy(strategyConfig);
generator.setTemplateEngine(new FreemarkerTemplateEngine());
// 开始生成
generator.execute();
}
}
自动填充
在进行插入或更新操作时,对字段自动填充。
使用:
-
在需要填充的字段上添加注解 TableField, 其属性 fill 来设定什么时候进行自动填充。
/** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;
-
继承自定义填充器MetaObjectHandler,实现两个方法(插入时填充,更新时填充)
@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("createTime", LocalDateTime.now(), metaObject); this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); } }
逻辑删除
逻辑删除:数据没有被删除,只是不会被展示出来;
使用:
-
先在数据库 配置好逻辑删除的实体字段名,并规定表示删除和未删除状态的字段值,如规定 delFlag = 1为已删除,delFlag = 0 为未删除;
-
再配置yaml文件:
mybatis-plus: global-config: db-config: logic-delete-field: delFlag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
乐观锁
并发操作时,我们需要保证对数据的操作不发生冲突。乐观锁就是其中一种方式。
乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。.因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
使用乐观锁时一般在表中增加一个version列。用来记录我们对每次记录操作的版本。每次对某条记录进行过操作是,对应的版本也需要+1。
在执行更新时: set version = 老版本+1 where version = 老版本
使用:
-
添加对应MP拦截器
@Configuration public class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //创建MP拦截器栈 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //分页拦截器 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); //乐观锁拦截器 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
-
在实体类的字段上加上
@Version
注解@Version private Integer version;
-
测试