MyBatis Plus

前言

1
2

参看官网:mybatis.plus

一、快速入门

1、创建数据库mybatis_plus

2、创建user

主键是bigint类型,不自增,为了后面测试雪花算法与自增问题。

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

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 Boot初始化

1
选择Sring Web模块即可。

4、导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>

注意:mybatis、mybatis plus不要同时导入,否则冲突,mybatis plus其实包含了mybatis的所有内容!

5、连接数据库

配置文件application.properties

1
2
3
4
5
6
7
# 5.x 驱动:com.mysql.jdbc.Driver
# 8.0 驱动:com.mysql.cj.jdbc.Driver,还需要时区配置

spring.datasource.username=用户名
spring.datasource.password=密码
spring.datasource.url=jdbc:mysql://localhost:3306/数据库名?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

6、使用MyBatis Plus

1
2
3
4
传统方式:
pojo-->dao[连接mybatis,配置mapper.xml]-->service-->controller
使用mybatis-plus后:
pojo-->mapper接口(一定要记得扫描)-->使用

pojo

1
2
3
4
5
6
7
8
9
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id; // Long类型,后面雪花算法生成的数值会很大
private String name;
private Integer age;
private String email;
}

Mapper

1
2
3
4
// 继承BaseMapper类,它编写了所有的CRUD操作
@Repository // 代表持久层
public interface UserMapper extends BaseMapper<User> {
}

扫描Mapper所在的包

1
2
3
4
5
6
7
@MapperScan("com.myself.mapper")  // 扫描我们的mapper文件夹
@SpringBootApplication
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
// 查询所有用户
// 参数是Wrapper条件构造器,但使用null可表示查询所有
userMapper.selectList(null).forEach(System.out::println);
}
}

二、配置日志

在配置文件中配置日志,可以更详细的看到SQL语句的执行过程。

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

三、CRUD、插件拓展

1
2
3
4
基本的CURD:通过id、ids、User、Map条件进行操作。

插件拓展:自动填充、乐观锁、分页查询、逻辑删除、性能分析、条件构造器、代码自动生成器
插件的使用基本流程是:注释+注册拦截器/编写处理器/配置文件

1、插入

1
2
3
4
5
6
User user = new User();
//user.setId(66L); // 没有设置id主键,但自动生成并插入(默认方式:ID_WORKER)
user.setName("学习Java");
user.setAge(17);
user.setEmail("study-self@qq.com");
System.out.println("插入记录数:" + userMapper.insert(user));

2、主键生成策略

1
2
3
4
5
6
7
8
9
10
11
@TableId(type = IdType.xxx)  // 看IdType源码
private Long id;

/*
AUTO(0), // 数据库id自增
NONE(1), // 未设置主键
INPUT(2), // 手动输入
ID_WORKER(3), // 全局唯一id(默认)
UUID(4), // 全局唯一id,uuid
ID_WORKER_STR(5); // ID_WROKER 字符串表示法
*/

分布式主键生成策略

1
2
3
4
5
6
1、数据库自增长序列或字段
2、UUID
3、UUID的变种
4、Redis生成ID
5、Twitter的snowflake算法
6、利用zookeeper生成唯一ID

ID_WORKER 全局唯一id

1
2
3
4
5
雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
可保证几乎全球唯一。

@Auto 主键自增

1
2
实体类属性上:@TableId(type = IdType.Auto)
数据库字段上:也必须自增

INPUT 手动输入

1
2
3
4
设置input后,
若手动输入,则使用输入的值;
若没有手动输入,就默认数据库字段自增;
若没有手动输入,且数据库没有自增,就报错。

3、更新

1
2
3
4
5
6
7
8
User user = new User();
// 根据id更新
user.setId(5L);
user.setName("雪花飘飘");
user.setAge(20);
// 通过条件自动拼接动态SQL
// 参数传入user对象
System.out.println("更新记录数:" + userMapper.updateById(user));

4、自动填充

1
2
3
4
5
6
7
8
9
创建时间、修改时间,一般需要自动化完成,不要手动更新;
阿里巴巴开发手册规范:所有的数据库表,几乎都要配置以下两个字段,且需要自动化
gmt_create // 创建时间
gmt_modified // 修改时间
因为我们需要追踪某个数据何时创建,何时修改的!
Greenwich Mean Time(GMT):格林尼治标准时间,也称全球统一时间

gmt_creat、gmt_modified一般用来作国际化、全球化的,
但我们自己常用:create_time、update_time,看起来更清晰明了。

方法一:数据库级别(一般工作中不允许修改数据库数据,了解即可)

1
2
3
4
在数据库表中添加字段:
列名 数据类型 默认值 更新 注释
create_time timestamp CURRENT_TIMESTAMP 是 创建时间
update_time同理。

表添加字段后,注意实体类同步属性!

1
2
private Date createTime;  // 数据库字段下划线,这里属性是驼峰命名
private Date updateTime;

方法二:代码级别

1、先将数据库的”默认值”、”更新”去掉。

1
同时,不用时间戳timestamp,改为datetime

2、实体类属性上加注解

1
2
3
4
@TableField(fill = FieldFill.INSERT)  // 插入操作时,字段添加填充内容
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入、更新
private Date updateTime;

3、注解fill需要处理器才能生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Slf4j  // 日志
@Component // 需要加入ioc容器
public class MyMetaObjectHandler implements MetaObjectHandler { // 实现这个处理器

// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill...");
// setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)
// metaObject传入的元数据
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}

// 更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill...");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
// 最后注意观察插入数据库的时间字段

5、乐观锁

1
2
乐观锁:很乐观,总是认为不会出现问题,无论干啥都不上锁,若出现问题,会再次更新值测试。
悲观锁:很悲观,总是认为会有问题,无论干啥都会上锁,才会去操作。

乐观锁实现方式

1
2
3
4
取出记录时,获取当前version
更新时,带上这个version
执行更新时,set version = new version where version = oldVersion
如果version不对,就更新失败
1
2
3
4
5
6
乐观锁:先查询,获得版本号【version=99
-- 线程A
update user set name="hello",【version=version+1】 where id=5 and 【version=99
-- 线程B
update user set name="hello",【version=version+1】 where id=5 and 【version=99
-- 如果B抢先完成更新,这时此条在数据库中的记录【version=100】,就会导致A更新失败!

测试MP(MyBatis Plus)的乐观锁插件

1、数据库表添加字段version

1
version int 默认值1

2、实体类同步属性version

1
2
@Version  // 乐观锁注解
private Integer version;

3、注册组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 要想让MP的乐观锁插件生效,需要注册组件(拦截器)。
/**
* 专门用来注册MyBatis Plus组件的。
* Optimistic 乐观
* Locker 锁
* Interceptor 拦截器
*/
@EnableTransactionManagement // 事务管理
@MapperScan("com.myself.mapper") // 扫描我们的mapper文件夹(配置类都可以扫描)
@Configuration // 配置类
public class MyBatisPlusConfig {

// 注册乐观锁插件(参考官网)
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}

6、查询

1
2
3
4
5
6
7
8
9
10
11
// 1、查询所有
List<User> users = userMapper.selectList(null);
// 2、通过Id查询
User user = userMapper.selectById(1L);
// 3、批量查询
List<User> users1 = userMapper.selectBatchIds(Arrays.asList(1, 2, 5, 88, 46));
// 4、条件查询 map
Map<String,Object> map = new HashMap<>();
map.put("id",5L);
map.put("name","雪花");
List<User> users2 = userMapper.selectByMap(map);

7、分页查询

1
2
3
* 原始的limit进行分页
* pageHelper第三方插件
* MP内置的分页插件

如何使用MP内置的分页插件?

1、注册拦截器组件

1
2
3
4
5
6
// 需要注册分页插件组件,也就是拦截器(参考官方)
// 分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}

2、直接使用Page对象

1
2
3
4
5
6
7
8
9
// 参数1:当前页、参数2:页面展示记录的数量
Page<User> page = new Page<>(2,4); // 第2页,显示4条记录。
userMapper.selectPage(page,null); // null查询所有
page.getRecords().forEach(System.out::println);// 最终第2页,只查到2条记录,因为只有7条记录。

// 分页插件,使得分页操作更简单了(自己探索page对象的方法)
System.out.println(page.getTotal()); // 满足条件的记录总数
System.out.println(page.getPages()); // 一共多少页(例如:共10条记录,每一页按3条显示,共4页)
System.out.println(page.getSize()); // 每一页显示的记录数

8、删除

1
2
3
4
5
6
7
8
// id,单条记录删除
userMapper.deleteById(12L);
// 通过id批量删除
userMapper.deleteBatchIds(Arrays.asList(8L,3L));
// 通过map删除
Map<String,Object> map = new HashMap<>();
map.put("name","雪花");
userMapper.deleteByMap(map);

9、逻辑删除

1
2
3
4
5
6
物理删除:从数据库中直接移除。
逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效。deleted=0 --> deleted=1

场景:管理员可以查看被删除的记录。(被删的还能查看?说明是逻辑删除。)
作用就是防止数据丢失,类似回收站。(物理删:清空回收站、逻辑删:把文件丢回收站里。)
再例如:注销 --> 确认注销

1、在数据库表中添加一个字段

1
2
字段名    类型  默认值
deleted int 0(0代表逻辑上没有删除,1代表逻辑上删除了,不管1或0在物理上是没有真正被删掉的)

2、实体类中同步属性

1
2
@TableLogic  // 逻辑删除注解
private Integer deleted;

3、注册逻辑删除组件

1
2
3
4
5
// 逻辑删除插件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}

4、写配置

配置文件application.properties

1
2
3
# 逻辑删除(逻辑上:1已删、0未删)
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

5、测试逻辑删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 删除
userMapper.deleteById(1L);
/*
查看控制台,逻辑删除使用的SQL语句是更新操作,而不是删除操作:
UPDATE user SET deleted=1 WHERE id=? AND deleted=0
在数据库中查看,你会发现这条记录还在,只是deleted由 0 改为 1 了,但后面查询的话是查不到该记录的。
*/

// 查询
System.out.println(userMapper.selectById(1L));
/*
控制台观察到的SQL语句是:
SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE id=? AND deleted=0
查询不到任何结果!
也就是说,查询的时候会自动的过滤被逻辑删除掉的字段(sql语句后面拼接:deleted=0)
*/

10、性能分析插件

1
2
3
4
5
开发中总会有一些慢SQL(性能太差的SQL语句,导致用时太长),
它们都可以通过测试、压测工具、druid等检测出来!

MP也提供了性能分析插件,如果超过这个时间就停止运行。
性能分析拦截器,用于输出每条SQL语句及其执行时间。

1、导入插件

1
2
3
4
5
6
7
8
9
// SQL执行效率插件
@Bean
@Profile({"dev","test"}) // 设置dev、test环境开启,保证我们的效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor pi = new PerformanceInterceptor();
pi.setMaxTime(100);//单位:ms,设置SQL最大执行时间,若超过则不执行且抛异常!开发设100ms最佳。
pi.setFormat(true); // 格式化控制台的SQL语句样式,可以更清晰观察它
return pi;
}

2、要在Spring Boot中配置文件中设置开发环境为dev或test

1
2
# 设置开发环境,激活
spring.profiles.active=dev

3、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
void contextLoads() {
// 查询所有用户
userMapper.selectList(null).forEach(System.out::println);
}
/* 控制台输出如下结果,用时30ms:
Time:30 ms - ID:com.myself.mapper.UserMapper.selectList
Execute SQL:
SELECT
id,
name,
age,
email,
create_time,
update_time,
version,
deleted
FROM
user
WHERE
deleted=0
*/

11、条件构造器

测试1

1
2
3
4
5
6
7
8
9
@Test
public void testWrapper01(){
// SQL语句:... WHERE name IS NOT NULL AND email IS NOT NULL AND age >= ?
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.ge("age",20);
mapper.selectList(wrapper).forEach(System.out::println);
}

测试2

1
2
3
4
5
6
7
8
9
@Test
public void testWrapper02(){
// SQL语句:... WHERE name = ?
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","jack");
// 查询一个数据,得到多个结果需要使用List、Map,而One仅限一个结果,否则报错。
User user = mapper.selectOne(wrapper);
System.out.println(user);
}

测试3

1
2
3
4
5
6
7
@Test
public void testWrapper03(){
// SQL语句:... where age between ? and ?
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",18,21);
mapper.selectList(wrapper).forEach(System.out::println);
}

测试4

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testWrapper04(){
// 模糊查询 like %xxx%
// 左 %xxx、右 xxx%
// SQL语句:... WHERE name LIKE '%o%' AND email LIKE 'te%'
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name","o")
.likeRight("email","te");
//List<User> users = mapper.selectList(wrapper); // 封装为对象
//List<Map<String,Object>> maps = mapper.selectMaps(wrapper); // 封装为Map
List<Object> objs = mapper.selectObjs(wrapper); // 封装为对象
objs.forEach(System.out::println);
}

测试5

1
2
3
4
5
6
7
8
@Test
public void testWrapper05(){
// 子查询
// SQL语句:... WHERE id IN (select id from user where id>3)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id>3");
mapper.selectList(wrapper).forEach(System.out::println);
}

其它

1
排序、分组看官网吧

12、代码自动生成器

1
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

1、导入依赖

由于版本问题,需要导入一个模板依赖!

1
2
3
4
5
6
<!-- velocity模板依赖 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>

2、编写代码生成器

注意:逻辑删除、版本控制等需要注册好相应组件

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
package com.myself;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
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.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.util.ArrayList;

/**
* @Author Lzq
* @Create 2020-07-25 12:11 AM
*/
public class AutoGeneratorTest {

public static void main(String[] args) {
// 代码生成器
AutoGenerator ag = new AutoGenerator();

// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("LZQ");
gc.setOpen(false);
gc.setFileOverride(true);
gc.setServiceName("%sService");
gc.setIdType(IdType.ID_WORKER); // 这个貌似不生效
gc.setDateType(DateType.ONLY_DATE);
//gc.setSwagger2(true); // 暂时先不生成这个,因为还需要导入依赖

ag.setGlobalConfig(gc);

// 数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("Aa960229aA");

ag.setDataSource(dsc);

// 包信息
PackageConfig pc = new PackageConfig();
pc.setParent("com.myself");
pc.setModuleName("generator"); // com.myself.generator.entyty
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setController("controller");

ag.setPackageInfo(pc);

// 策略配置
StrategyConfig sc = new StrategyConfig();
sc.setInclude("user"); // 映射的表名,可多个
sc.setNaming(NamingStrategy.underline_to_camel);
sc.setColumnNaming(NamingStrategy.underline_to_camel);
sc.setEntityLombokModel(true);
sc.setLogicDeleteFieldName("deleted");
sc.setVersionFieldName("version");
sc.setRestControllerStyle(true);
sc.setControllerMappingHyphenStyle(true); // localhost:8080/hello_id_2
// 自动填充策略
TableFill create_time = new TableFill("create_time", FieldFill.INSERT);
TableFill update_time = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tf = new ArrayList<>();
tf.add(create_time);
tf.add(update_time);
sc.setTableFillList(tf);

ag.setStrategy(sc);

// 执行
ag.execute();
}
}