一、介绍

1、简介

官网:https://mp.baomidou.com/

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

2、特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作

  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

  • 支持关键词自动转义:支持数据库关键词(order、key……)自动转义,还可自定义关键词

  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

二、环境搭建

1、创建数据库

1
create database mybatis_plus;

2、创建 User 表

表结构:

image-20210222225211346

数据库 Schema 脚本

1
2
3
4
5
6
7
8
9
10
DROP TABLE IF EXISTS user;

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)
);

数据库 Data 脚本

1
2
3
4
5
6
7
8
9
DELETE FROM user;

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');

3、创建项目

初始化工程

​ 使用 Spring Initializr 快速初始化一个 Spring Boot 工程

添加依赖

添加 :spring-boot-starter、spring-boot-starter-test、 mybatis-plus-boot-starter、MySQL、lombok

lombok: 在项目中使用Lombok可以减少很多重复代码的书写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

注意:

  • 引入Mybatis-Plus 之后请不要再次引入 Mybatis 以及 Mybatis-Spring,以避免因版本差异导致的问题。
  • 使用lombok 在IDEA 中一定要安装插件,新的idea 版本一般默认都安装插件,如果使用的较低版本,需要手动安装一下。

image-20210222230239817

4、配置

application.properties (请按版本来选择)

  • mysql5
1
2
3
4
5
#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus
spring.datasource.username=root
spring.datasource.password=123456
  • mysql8以上(spring boot 2.1)

    注意:driver和url的变化

1
2
3
4
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为Spring Boot 2.1 集成了 8.0版本的jdbc驱动,这个版本的 jdbc 驱动需要添加这个后缀,否则运行测试用例报告如下错误:


java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more


这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱动,之前的 com.mysql.jdbc.Driver 已经被废弃,否则运行测试用例的时候会有 WARN 信息

三、MP入门-编写代码

1、主类

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹

注意:扫描的包名根据实际情况修改

1
2
3
4
5
6
7
@SpringBootApplication
@MapperScan("com.oy.mp.mapper")
public class MPApplication {
public static void main(String[] args) {
SpringApplication.run(MPApplication.class, args);
}
}

2、实体

创建包 entity 编写实体类 User.java(此处使用了 Lombok 简化代码)

1
2
3
4
5
6
7
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}

查看编译结果

image-20210222230951281

3、mapper

创建包 mapper 编写Mapper 接口: UserMapper.java

1
2
3
@Component
public interface UserMapper extends BaseMapper<User> {
}

4、测试

添加测试类,进行功能测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RunWith(SpringRunner.class)
@SpringBootTest
public class MPApplicationTests {

@Autowired
private UserMapper userMapper;

@Test
public void testSelectList(){
// UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper
// 所以不填写就是无任何条件
List<User> users = userMapper.selectList(null);
users.forEach(System.err::println);
}
}

image-20210222231328938

5、配置日志

查看sql输出日志

1
2
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

image-20210222231844815

四、MP-对表的CRUD操作

1、insert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RunWith(SpringRunner.class)
@SpringBootTest
public class CRUDTests {

@Autowired
private UserMapper userMapper;

@Test
public void testInsert(){

User user = new User();
user.setName("Helen");
user.setAge(18);
user.setEmail("55317332@qq.com");

int result = userMapper.insert(user);
System.out.println(result); //影响的行数
System.out.println(user); //id自动回填
}
}

注意: 数据库插入 id 值默认认为: 全局唯一的 id

image-20210222232148312

2、主键策略

  • ID_WORKER

    MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

/**
* <p>名称:IdWorker.java</p>
* <p>描述:分布式自增长ID</p>
* <pre>
* Twitter的 Snowflake JAVA实现方案
* </pre>
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
* 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
* 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
* 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
* 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
* 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
* 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
* <p>
* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
* @author Polim
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;

private final long workerId;
// 数据标识id部分
private final long datacenterId;

public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}

if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;

return nextId;
}

private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}

private long timeGen() {
return System.currentTimeMillis();
}

/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}

/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
}

User 配置注解

1
2
3
4
5
6
7
8
9
@Data
public class User {
// mp 默认id的生成策略是Long类型的 idWorker, 全局唯一的
@TableId(type = IdType.ID_WORKER)
private Long id;
private String name;
private Integer age;
private String email;
}
  • 自增策略

    要想主键自增需要配置如下主键策略

    • 需要在创建数据表的时候设置主键自增
    • 实体字段中配置 @TableId(type = IdType.AUTO)

3、update

3.1 根据Id更新操作

注意:update时生成的sql自动是动态sql:UPDATE user SET age=? WHERE id=?

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testUpdateById(){

User user = new User();
user.setId(1L);
user.setAge(28);

int result = userMapper.updateById(user);
System.err.println(result);

}

3.2 自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:

  1. 数据库表中添加自动填充字段

在User表中添加datetime类型的新的字段 create_timeupdate_time

image-20210223222937336

  1. 实体上添加注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class DataMetaObjctHandler implements MetaObjectHandler {
// 在执行insert语句的时候被拦截操作的
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}

// 修改语句
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
  1. 测试

image-20210223225102359

3.3 乐观锁

主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新

乐观锁实现方式:

  • 取出记录时,获取当前当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对, 就更新失败
  1. 数据库添加 version 字段

image-20210223230004035

  1. 实体类添加 version 字段

    并添加 @Version 注解

1
2
3
4
// 在User类中 乐观锁修改版本号
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
  1. 元对象处理接口添加 version 的 insert 默认值
1
2
3
4
5
@Override
public void insertFill(MetaObject metaObject) {
......
this.setFieldValByName("version", 1, metaObject);
}

特别说明:

  • 支持的数据类型只有 int、Integer、long、Long、Date、Timestamp、LocalDataTime
  • 整型类型下 newVersion = oldVersion + 1
  • newVersion 会回到 entity 中
  • 仅支持 updateById(id) 与 update(entity,wrapper) 方法
  • 在 update(entity, wrapper) 方法下,wrapper 不能复用
  1. 在 MybatisPlusConfig 中注册 Bean

创建配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@EnableTransactionManagement
@MapperScan("com.oy.mp.mapper")
public class MPConfig {

/***
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
  1. 测试乐观锁可以修改成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 测试 乐观锁插件
*/
@Test
public void testOptimisticLocker(){

// 查询
User user = userMapper.selectById(1L);

// 修改数据
user.setName("Helen Yao");
user.setEmail("helen@qq.com");
user.setAge(21);

// 执行更新
int result = userMapper.updateById(user);
System.err.println(result);

}

image-20210223233442073

4、select

4.1 根据id查询记录

1
2
3
4
5
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.err.println(user);
}

4.2 通过多个id批量查询

​ 动态sql的foreach的功能

1
2
3
4
5
6
7
8
9
10
@Test
public void testSelectBatchIds() {
List list = new ArrayList();
list.add(1L);
list.add(2L);
list.add(3L);
List users = userMapper.selectBatchIds(list);
users.forEach(System.err::println);

}

4.3 简单的条件查询

​ 通过map 封装查询条件

1
2
3
4
5
6
7
8
9
10
@Test
public void testSelectByMap(){

Map<String, Object> map = new HashMap<>();
map.put("name","Helen");
map.put("age",18);
List<User> users = userMapper.selectByMap(map);

users.forEach(System.err::println);
}

注意: map 中的key 对应的是数据库中的列名。例如数据库 user_id, 实体类是userId, 这时 map 的 key 需要填写 user_id。

4.4 分页

​ Mybatis Plus 自带分页插件,只要简单的配置即可实现分页功能

创建配置类

此时是可以删除主类中的 @MapperScan 扫描注解

1
2
3
4
5
6
7
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}

测试 selectPage 分页

测试:最终通过 page 对象获取相关数据

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testSelectPage(){
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page,null);

page.getRecords().forEach(System.err::println);
System.err.println("当前页:"+page.getCurrent());
System.err.println("总页数:"+page.getPages());
System.err.println("每页显示记录数:"+page.getSize());
System.err.println("总记录数:"+page.getTotal());
System.err.println("是否有下一页:"+page.hasNext());
System.err.println("是否有上一页:"+page.hasPrevious());
}

image-20210224105954327

测试 selectMapsPage 分页: 结果集是 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testSelectMapsPage(){
Page<User> page = new Page<>(1,5);

IPage<Map<String, Object>> Imap = userMapper.selectMapsPage(page, null);

// 注意,此行必须使用 Imap 获或记录列表,否则会与数据类型的转换错误
Imap.getRecords().forEach(System.err::println);
System.err.println(page.getCurrent());
System.err.println(page.getSize());
System.err.println(page.getTotal());
System.err.println(page.hasNext());
System.err.println(page.hasPrevious());
}

image-20210224111055158

5、delete

5.1 根据 id 删除记录

1
2
3
4
5
@Test
public void testDeleteById(){
int result = userMapper.deleteById(1364225405027991553L);
System.err.println(result);
}

5.2 批量删除

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testDeleteBatchIds(){
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(2L);
list.add(3L);

int result = userMapper.deleteBatchIds(list);
System.err.println(result);

}

5.3 简单的条件查询删除

1
2
3
4
5
6
7
8
9
10
11
 @Test
public void testDeleteByMap(){

Map<String, Object> map = new HashMap<>();
map.put("name","Helen");
map.put("age",18);

int result = userMapper.deleteByMap(map);
System.err.println(result);

}

5.4 逻辑删除

  • 物理删除: 真实删除, 将对应色数据库中删除,之后查询不到此条被删除数据
  • 逻辑删除: 假删除,将对应的数据中是否被删除字段状态修改为”被删除状态”,之后在数据库中仍旧能看到条数据记录

在数据库中添加delete 字段

image-20210224113021529

实体类添加deleted 字段

​ 并加上 @TableLogic 注解 和 @TableField(fill = FieldFill.INSERT) 注解

1
2
3
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;

元对象处理器接口添加deleted的insert默认值

1
2
3
4
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("deleted",0,metaObject);
}

image-20210224113528212

application.properties 加入配置

此为默认值,如果你的默认值和mp默认的一样,该配置可无

1
2
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

在 MybatisPlusConfig 中注册 Bean

1
2
3
4
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}

测试逻辑删除

  • 测试后发现,数据并没有被删除,deleted 字段的值 0 变成 1
  • 测试后分析打印的 sql 语句,是一条 update
  • 注意:被删除数据的deleted 字段的值必须是0, 才能被选取出来执行逻辑删除的操作
1
2
3
4
5
6
7
8
/**
* 测试 逻辑删除
*/
@Test
public void testLogicDelete(){
int result = userMapper.deleteById(1L);
System.err.println(result);
}

image-20210224114757627

image-20210224114817559

测试逻辑删除后的查询

MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断

1
2
3
4
5
6
7
8
9
10
/**
* 测试 逻辑删除后的查询:
* 不包括被逻辑删除的记录
*/
@Test
public void testLogicDeleteSelect() {
User user = new User();
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}

image-20210224115125306

测试后分析打印的sql语句,包含 WHERE deleted=0

SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0

6、性能分析

​ 性能分析拦截器,用于输出每条 SQL 语句及其执行时间

​ SQL 性能执行分析,开发环境使用,超出指定时间,停止运行。有助于发现问题

6.1 配置插件

参数说明

  • 参数: maxTime: SQL 执行最长时长,超出自动停止运行,有助于发现问题。
  • 参数: format: SQL是否格式化,默认 false。

在 MybatisPlusConfig 中配置

1
2
3
4
5
6
7
8
9
@Bean
@Profile({"dev","test"}) // 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
// performanceInterceptor.setMaxTime() ms, 超出此设置的ms则sql不执行
performanceInterceptor.setMaxTime(100);
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}

Spring Boot 中设置dev环境

1
2
#环境设置: dev、test、prod
spring.profiles.active=dev

可以针对各环境新建不同的配置文件 application-dev.propertiesapplication-test.propertiesapplication-prod.properties

也可以自定义环境名称: 如test1、test2

6.2 测试

常规测试

1
2
3
4
5
6
7
8
9
10
11
/**
* 测试 性能分析插件
*/
@Test
public void testPerformance(){
User user = new User();
user.setName("Jarry");
user.setEmail("Jarry@gmail.com");
user.setAge(23);
userMapper.insert(user);
}

image-20210224180456351

将maxTime 改小之后再次进行测试

performanceInterceptor.setMaxTime(5);//ms,超过此处设置的ms不执行

如果执行时间过长,则抛出异常:The SQL execution time is too large,

image-20210224180649328

五、条件构造器

如果想进行复杂条件查询,那么需要使用条件构造器 Wapper,涉及到如下方法

  1. delete

  2. selectOne

  3. selectCount

  4. selectList

  5. selectMaps

  6. selectObjs

  7. update

1、wrapper介绍

image-20210224181241835

  • Wrapper : 条件构造抽象类,最顶端父类

    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

      • QueryWrapper : Entity 对象封装操作类,不是用lambda语法
      • UpdateWrapper : Update 条件封装,用于Entity对象更新操作
    • AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。

      • LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper

      • LambdaUpdateWrapper : Lambda 更新封装Wrapper

查询方式说明
setSqlSelect设置 SELECT 查询字段
whereWHERE 语句,拼接 + WHERE 条件
andAND 语句,拼接 + AND 字段=值
andNewAND 语句,拼接 + AND (字段=值)
orOR 语句,拼接 + OR 字段=值
orNewOR 语句,拼接 + OR (字段=值)
eq等于=
allEq基于 map 内容等于=
ne不等于<>
gt大于>
ge大于等于>=
lt小于<
le小于等于<=
like模糊查询 LIKE
notLike模糊查询 NOT LIKE
inIN 查询
notInNOT IN 查询
isNullNULL 值查询
isNotNullIS NOT NULL
groupBy分组 GROUP BY
havingHAVING 关键词
orderBy排序 ORDER BY
orderAscASC 排序 ORDER BY
orderDescDESC 排序 ORDER BY
existsEXISTS 条件语句
notExistsNOT EXISTS 条件语句
betweenBETWEEN 条件语句
notBetweenNOT BETWEEN 条件语句
addFilter自由拼接 SQL
last拼接在最后,例如:last(“LIMIT 1”)

2、示例

ge、gt、le、lt、isNull、isNotNull

1
2
3
4
5
6
7
8
9
@Test
public void testDelete(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("name")
.ge("age",12)
.isNotNull("email");
int result = userMapper.delete(queryWrapper);
System.err.println("delete return count=" + result);
}

测试结果:

1
SQLUPDATE user SET deleted=1 WHERE deleted=0 AND name IS NULL AND age >= ? AND email IS NOT NULL

eq、ne

1
2
3
4
5
6
7
8
9
@Test
public void testSelectOne(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();

queryWrapper.eq("name","Jarry");

User user = userMapper.selectOne(queryWrapper);
System.err.println(user);
}

注意: selectOne 返回的是一条实体记录,当出现多条时会报错

测试结果:

1
SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ?