MyBatis-Plus-01笔记(一)
MyBatis-Plus-01 (简称 MP) 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
支持的数据库:任何能使用 mybatis
进行 crud, 并且支持标准 sql 的数据库
Hello MyBatis-Plus
基于 SpringBoot 项目,数据库使用 MySql
Maven 具体以来如下:
1 | <dependencies> |
所链接库表结构如下
application.yaml 配置文件配置信息如下:
1 | spring: |
开启 Mapper 文件扫描
1 |
|
编写 ROM 数据库映射实体类
1 | // Lombok生成标准函数 |
创建 mapper 文件
1 | // 声明说一个持久层类,并在 spring 容器中创建对象 |
测试类
1 |
|
输出数据库数据及完成操作
主键生成策略
系统唯一 Id 是在设计一个系统的时候常常会遇见的问题,生成 Id 的方法有很多,适应不同的场景、需求以及性能要求
MyBatis-Plus-01 中默认使用雪花算法生成唯一 Id
雪花算法:snowflake 是 Twitter 开源的分布式 Id 生成算法,结果是一个 long
型的 Id
具体构成:使用41bit作为毫秒数,10bit作为机器的Id(5个bit是数据中心,5个bit的机器Id),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 Id),最后还有一个符号位,永远是0
设置插入输入时自增 Id 的策略
1 | // 在主键上设置 type,属性,MyBatis-Plus-01 已将相应策略封装成枚举类型 IdType |
插入操作
1 |
|
更新操作
1 |
|
自动填充
对于数据表的创建时间(create_time)和修改时间(update_time)一般都是自动化完成的
数据库级别
首先为表添加两个字段,数据库中为 create_time
,update_time
,类型为 TimeStamp
,默认值为 CURRENT_TIMESTAMP
,对于update_time
选择根据当前时间戳更新
在 pojo 实体类中
1 |
|
执行上述更新操作,发现时间自动填充
代码级别
实际开发中更倾向于代码级别的操作
先将上述 数据库级别 操作的设置默认值和自更新设置去除
在 pojo 中设置自动填充策略
1
2
3
4
5
private LocalDateTime createTime;
private LocalDateTime updateTime;具体填充策略分别如下
1
2
3
4
5
6
7
8
9
10
11// 字段填充策略枚举类
public enum FieldFill {
// 默认不处理
DEFAULT,
// 插入时填充字段
INSERT,
// 更新时填充字段
UPDATE,
// 插入和更新时填充字段
INSERT_UPDATE
}实现元对象处理器接口:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyMetaObjectHandler implements MetaObjectHandler {
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
// 插入数据时字段都更新
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
// 更新操作时,只有更新字段更新
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}使用上述插入和更新操作进行测试
锁
乐观锁/悲观锁
乐观锁
顾名思义,十分乐观,无论干什么都不去上锁
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
测试 MyBatis-Plus-01 的乐观锁插件
给表加上一个 version 字段,支持的数据类型只有
int, Integer, long, Long, Date, Timestamp, LocalDateTime
同时在实体类中添加相应字段,并添加相应注解
@Version
表明该字段是乐观锁注册组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 自动管理事务,默认开启
public class MyBatisPlusConfig {
// 注册乐观锁插件
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}测试
具体的操作是先取出当前的 version ,再在执行更新或其他将 version 值传入后,再执行更新操作
测试乐观锁成功测试案例
1
2
3
4
5
6
7
8
9// 测试乐观锁成功
public void testOptimisticLockerSucc() {
// 查询用户信息
Student student = studentMapper.selectById("95001");
// 修改用户信息
student.setSdept("CS");
int update = studentMapper.updateById(student);
}疑问,为什么要自己手动获取或传入 version,直接在执行更新操作前内部取出相应 version 不是更合理吗,有的想不通。
上述测试案例还会导致一个问题——
updateTime
字段自动填充失败具体原因:在
updateById
方法中所传的实体参数,针对自动填充的字段- 如果字段值非空,则按照所传的值更新
- 如果字段值为空,则按照自动填充的规则更新
从源码来看是因为编写填充策略时具体调用等方法有关
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyMetaObjectHandler implements MetaObjectHandler {
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}对于官网推荐的三种写法
strictUpdateFill, strictUpdateFill, fillStrategy
strictFillStrategy
只有非空才能自动填充而如果使用
setFieldValByName
的话,不需要判断该属性是否为空,直接覆盖而在上述案例中,传入的
student
对象的update_time
属性是非空的,故直接赋值了过去而并未更新时间解决方案:
最简单的就是将要修改的值封装成一个新对象,然后对于时间等需要自动填充属性设置为空即可
编写具体填充策略实现时选择
setFieldValByName
的写法1
2
3
4
5
6
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
//this.strictUpdateFill(metaObject, "gmtUpdate", LocalDateTime.class, LocalDateTime.now());
this.setFieldValByName("gmtUpdate", LocalDateTime.now(), metaObject);
}然后使用时正常调用即可
测试乐观锁失败测试案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 测试乐观锁失败
public void testOptimisticLockerFail() {
// 模拟多线程操作
// 查询用户1信息
Student student1 = studentMapper.selectById("95001");
// 模拟多线程,线程插队现象
// 查询用户2信息
Student student2 = studentMapper.selectById("95001");
// 修改用户2信息
student2.setSdept("IS");
studentMapper.updateById(student2);
// 修改用户1信息
student1.setSdept("MA");
studentMapper.updateById(student1);
}最终 sdept 值为 IS ,对 student1 进行更新时 version 值错乱,导致更新失败
悲观锁
与乐观锁相反,无论干什么都要上锁
分页查询
MyBatis-Plus-01 内置分页插件,具体使用步骤如下
注册分页插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 自动管理事务,默认开启
public class MyBatisPlusConfig {
// 注册插件
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 注册乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 注册分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}测试
1
2
3
4
5
6
7
8
9
public void testPageHelper() {
Page<Student> objectPage = new Page<>(1, 4);
studentMapper.selectPage(objectPage, null);
List<Student> records = objectPage.getRecords();
records.forEach(System.out::println);
}
逻辑删除
即软删除,增加一个字段作为当前行是否存在的标记
使用步骤
在表中增加一个
deleted
字段,可以直接设定一个默认值为 1代表该条数据存在,或者在自动填充的的 handle实现类中声明1
2
3
4
5
6
7
8
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// 自动填充软删除默认状态
this.strictInsertFill(metaObject, "deleted", Integer.class, 1);
}在实体类中声明该字段的映射,并添加注解
@TableLogic
配置配置文件 application.yaml
1
2
3
4
5
6# 配置软删除
global-config:
db-config:
logic-delete-field: delete
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)测试
执行 SQL 分析打印
该功能依赖
p6spy
组件,完美的输出打印 SQL 及执行时长3.1.0
以上版本
具体使用步骤如下
导入依赖
1
2
3
4
5<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>修改 application.yaml 配置文件
1
2
3
4
5
6spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
username: root
password: xxxxxxxx
url: jdbc:p6spy:mysql://localhost:3306/stu更换数据哭驱动,且在 url 上添加 p6spy
创建配置文件 spy.properties,具体配置信息如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2测试即可得到分析结果打印
条件构造器
具体参考官方文档
代码生成器
用于自动生成模版代码
具体使用步骤
导入依赖,对于
MyBatis-Plus-01-generator
,官网使用的是 3.4.2 版本,但是导入失败,查了一下 Maven,好像还没上传,最新职业 3.4.1 版本
1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>com.baomidou</groupId>
<artifactId>MyBatis-Plus-01-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>编写代码生成器代码,可以直接参照官网,以下是我自己的模板
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// 代码自动生成器
public class CodeGenerator {
public static void main(String[] args) {
// 构建 代码自动生成器 对象
AutoGenerator autoGenerator = new AutoGenerator();
// 配置策略
// 1. 全局配置
GlobalConfig globalConfig = new GlobalConfig();
// 输出路径
String projectPath = System.getProperty("user.dir");
globalConfig.setOutputDir(projectPath + "/code-generator/src/main/java");
// 输出作者
globalConfig.setAuthor("秋寒");
// 是否打开输出目录
globalConfig.setOpen(false);
// 是否覆盖已有文件
globalConfig.setFileOverride(false);
// 设置 service 文件名称方式
globalConfig.setServiceName("%sService");
// 设置主键生成策略
globalConfig.setIdType(IdType.ASSIGN_ID);
// 设置日期类型
globalConfig.setDateType(DateType.ONLY_DATE);
// 设置开启 swagger2 模式
globalConfig.setSwagger2(true);
// 将全局配置放入 代码生成器 对象中
autoGenerator.setGlobalConfig(globalConfig);
// 2. 设置数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL);
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/stu?serverTimezone=GMT%2B8");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("xxxxx");
// 将数据库配置放入代码生成器中
autoGenerator.setDataSource(dataSourceConfig);
// 3. 包的配置
PackageConfig packageConfig = new PackageConfig();
// packageConfig.setModuleName("code-generator");
packageConfig.setParent("cn.hznu");
packageConfig.setEntity("pojo");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setController("controller");
// 将包配置放入代码生成器中
autoGenerator.setPackageInfo(packageConfig);
// 4. 策略配置
StrategyConfig strategy = new StrategyConfig();
// 设置命名格式为 下划线转驼峰
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 自动使用 Lombok
strategy.setEntityLombokModel(true);
// 设置要映射的表名
strategy.setInclude("student"); // 可变参数,多表直接夹在后面即可
// 设置软删除字段名
strategy.setLogicDeleteFieldName("deleted");
// 设置自动填充策略
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
// 将填充策略放入策略配置中
strategy.setTableFillList(Arrays.asList(createTime, updateTime));
// 设置乐观锁
strategy.setVersionFieldName("version");
// 开启 Rest 风格的驼峰命名
strategy.setRestControllerStyle(true);
// 设置请求链接下划线命名
strategy.setControllerMappingHyphenStyle(true);
// 将策略配置放入代码生成器中
autoGenerator.setStrategy(strategy);
// 生成
autoGenerator.execute();
}
}观察结果,已在制定目录生成成功
总计:很快捷的开发工具