MyBatis-Plus 学习笔记
MyBatis-Plus ( 简称 MP ) 是一款基于 MyBatis 的增强工具,他并不是由官方提供,而是由个人开发出来的一款增强框架,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生,后面 mybatis-plus 会简称为 MP
官方自述
无侵入 :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小 :启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作 :内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用 :通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成 :支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式 :支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作 :支持全局通用方法注入( Write once, use anywhere )
内置代码生成器 :采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件 :基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库 :支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件 :可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件 :提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速入门初体验
跟着官方文档进行一次 MP 的快速入门体验:
前置条件准备
**1.**创建数据库
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 DROP TABLE IF EXISTS `USER `;DROP TABLE IF EXISTS T_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) );INSERT INTO `USER `(ID, `NAME`, AGE, EMAIL) VALUES (1 , '张三' , 18 , 'test1@qq.com' ), (2 , '王富贵' , 20 , 'test2@qq.com' ), (3 , '老八' , 28 , 'test3@qq.com' ), (4 , '马云' , 21 , 'test4@qq.com' ), (5 , '王健林' , 24 , 'test5@qq.com' ); RENAME TABLE USER TO T_USER;ALTER TABLE T_USER ADD CREATE_TIME DATETIME DEFAULT NOW();ALTER TABLE T_USER ADD CHANGE_TIME DATETIME DEFAULT NOW();ALTER TABLE T_USER ADD VERSION INT DEFAULT 1 ;ALTER TABLE T_USER ADD DELETED INT (1 ) DEFAULT 1 ;
**2.**快速创建一个 springboot 的程序引入相关的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.49</version > <scope > runtime</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.4.0</version > </dependency >
**3.**配置数据库连接
1 2 3 4 5 6 spring.datasource.driver-class-name =com.mysql.jdbc.Driver spring.datasource.url =jdbc:mysql:///zhe spring.datasource.username =root spring.datasource.password =****** mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl
开始编码
在我们把数据库以及程序都布置好了之后,我们尝试做一次简单的查询:
**1.**创建实体类以及 dao 接口
1 2 3 4 5 6 7 8 @Data public class User { private Long id; private String name; private Integer age; private String email; }
**2.**创建 dao 接口,这里需要注意 接口类需要继承 BaseMapper
抽象类并填写实体类对应泛型 ,然后就不用管了
1 2 3 4 @Mapper @Repository public interface UserDao extends BaseMapper <User> { }
**3.**接下来我们在测试类中书写代码即可:
1 2 3 4 5 6 7 8 9 10 11 12 @SpringBootTest class Springboot01MybatisPlusApplicationTests { @Autowired private UserDao ud; @Test void contextLoads () { ud.selectList(null ).forEach( item->System.out.println(item) ); } }
查询就这么简单的完成了!
快速CRUD练习
insert 插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringBootTest class Springboot01Test { @Autowired private UserDao ud; @Test void contextLoads () { User user = new User (); user.setName("张涵哲" ); user.setAge(50 ); user.setEmail("zhang_hanzhe@qq.com" ); ud.insert(user); } }
update 更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringBootTest class Springboot01Test { @Autowired private UserDao ud; @Test void contextLoads () { User user = new User (); user.setName("张涵哲" ); user.setAge(21 ); user.setEmail("zhang_hanzhe@qq.com" ); ud.updateById(user); } }
delete 删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @SpringBootTest class Springboot01Test { @Autowired private UserDao ud; @Test void contextLoads () { ud.deleteById(1 ); ud.deleteBatchIds(Arrays.asList(2 , 3 , 4 , 5 )); HashMap<String, Object> map = new HashMap <>(); map.put("name" , "张涵哲" ); ud.deleteByMap(map); } }
select 查询
由于刚刚的测试将数据都删光了,这里建议在执行一次初始化的 SQL 重置一下数据,然后在跟着测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @SpringBootTest class Springboot01Test { @Autowired private UserDao ud; @Test void contextLoads () { System.out.println(ud.selectById(1L )); System.out.println(ud.selectBatchIds(Arrays.asList(1 , 2 , 3 ))); System.out.println(ud.selectList(null )); HashMap<String, Object> map = new HashMap <>(); map.put("name" , "老八" ); System.out.println(ud.selectByMap(map)); System.out.println(ud.selectCount(null )); } }
常用注解学习
TableName注解
刚刚我们快速过了一遍 CRUD,表名和实体类是对应的,如果我们稍作修改的话再来尝试一下:
表名:T_USER
,实体类名:UserEntity
1 2 3 4 5 6 7 org.springframework.jdbc.BadSqlGrammarException: # # # # # ......
这里就可以发现程序报错了,因为他的 SQL 是自动生成的,自动生成的表明默认按照实体类的驼峰转下划线完成的,那么这里就需要使用 @TableName
来重新配置一下了
1 2 3 @Data @TableName("T_USER") public class UserEntity { ... }
查询成功!该注解主要用于实体类和表名称的绑定,除此之外还有其他功能但并不常用,如有需要去查官方文档
TableId 主键注解
仔细看之前的插入语句会发现有一个细节:==我们并没有为这个对象设置 ID,但是数据库中却存进去了 ID==,这是因为 MP 会对表中的主键自动生成一个 ID,关于主键自动填充我们需要了解一下 @TableId
主键注解
@TableId
默认 value
对应的是主键字段名称,第二个type
是枚举类型,用来修改自动生成类型:
IdType.AUTO
:对应数据库中的自增
IdType.NONE
:不对主键进行任何处理
IdType.INTUT
:我们在执行插入之前手动设置值
IdType.ASSIGN_ID
:使用雪花算法 计算 ID
IdType.ASSIGN_UUID
:分配 UUID
修改自动生成类型
==使用 AUTO 类型必须将数据库中的自增开启==,剩下的类型如法炮制,就不写了
1 2 3 4 5 6 7 8 9 10 @Data @TableName("T_USER") public class UserEntity { @TableId(value = "ID", type = IdType.AUTO) private Long aaa; private String name; private Integer age; private String email; }
TableField 非主键注解
既然由主键注解,当然也就有非主键注解 @TableField
,一般用来绑定库内的字段名,还可以进行一些高级操作
自动填充
在我们创建表的时候,有的表会涉及到时间列,包含创建时间以及修改时间,这两个我们可以通过 SQL 写死,也可以在 java 中创建一个 Date 实例丢给他,学习了 MP 之后我们就可以使用自动填充了 ( 自己创建好两个列 ):
1. TableField
注解中的 fill
表示自动填充,使用自动填充一定要知道什么情况下被填充,填充的内容是什么
1 2 3 4 5 6 7 8 9 10 11 12 @Data @TableName("T_USER") public class UserEntity { ...... @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date changeTime; }
**2.**实体类配置好自动填充时机时候,我们还要配置一下自动填充的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration public class FillConfig implements MetaObjectHandler { @Override public void insertFill (MetaObject metaObject) { this .strictInsertFill(metaObject, "createTime" , Date.class, new Date ()); this .strictUpdateFill(metaObject, "changeTime" , Date.class, new Date ()); } @Override public void updateFill (MetaObject metaObject) { this .strictUpdateFill(metaObject, "changeTime" , Date.class, new Date ()); } }
非表中字段
实体类中并不是每个属性都是表中的字段,例如表中的性别字段,1代表男,0代表女,区分起来就很麻烦,为了解决这个问题我们需要在类中定义两个常量,再告诉 MP 这不是表中的字段,就解决了这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Data @TableName("T_USER") public class UserEntity { @TableId(value = "ID", type = IdType.AUTO) private Long aaa; private String name; private Integer age; private Integer gender; private String email; @TableField(exist = false) public static final int MALE = 1 ; @TableField(exist = false) public static final int FEMALE = 0 ; }
Version 乐观锁
所谓乐观锁就是在数据库中添加一个列作为标识列,一般为 version,在进行 upd ate 之前先进行 select 查询,如果查询的 version 和更新时的 version 是一致的就证明没人操作过这行记录,那么就进行修改,并且修改 version 列,如果查询时和修改时的列不一致就证明有人进行了修改,为了防止脏读就会取消更新。
测试乐观锁
在使用乐观锁之前我们需要添加乐观锁插件:
1 2 3 4 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor () { return new OptimisticLockerInterceptor (); }
乐观锁对应注解为 @Version
,没有任何参数,如果变量名与字段名对应不上可以使用 @TableFiels
注解进行绑定,记得在测试之前要在数据库中添加标识字段 ( 建议使用 version ):
1 2 3 4 5 6 7 8 @Data @TableName("T_USER") public class UserEntity { ...... @Version private Integer version; }
只需要这两步,这样一来乐观锁就配置完成了,现在我们来开始测试 ( version 默认是 1 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @SpringBootTest class Springboot01Test { @Autowired private UserDao ud; @Test void contextLoads () { Integer version = ud.selectById(2L ).getVersion(); UserEntity user = new UserEntity (); user.setAaa(2L ); user.setEmail("fu_gui@qq.com" ); user.setVersion(version); ud.updateById(user); } }
测试运行成功!version 字段被修改成了 2,再次测试一样成功!version 变成 3,按照之前分析的逻辑每次更新时 version 都会发生变化,现在我们来模拟一次更新失败的情况:
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 @SpringBootTest class Springboot01Test { @Autowired private UserDao ud; @Test void contextLoads () { Integer v1 = ud.selectById(2L ).getVersion(); UserEntity user1 = new UserEntity (); user1.setAaa(2L ); user1.setEmail("update1@qq.com" ); user1.setVersion(v1); Integer v2 = ud.selectById(2L ).getVersion(); UserEntity user2 = new UserEntity (); user2.setAaa(2L ); user2.setEmail("update2@qq.com" ); user2.setVersion(v2); System.out.println(ud.updateById(user2)); System.out.println(ud.updateById(user1)); } }
输出结果 1 和 0,代表第一次执行成功了,第二次执行失败了,查看数据库,邮箱也是留在了 update2,成功!
TableLogic 逻辑删除
当我们在页面中删除某个数据的时候,往往不是真的删除,而是通过修改某个字段让用户不在读取,因为在某些业务中被删除的数据也有存在的价值
在 MP 中使用逻辑删除也非常的简单,当然我们需要在添加一个列作为逻辑删除列 ( 这里推荐使用 deleted ),且所有表中的逻辑删除字段都要使用这个名字,然后来约定一个值,我这里就是 0 代表已删除,1 代表正常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Data @TableName("T_USER") public class UserEntity { @TableId(value = "ID", type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date changeTime; @TableLogic private Integer deleted; }
实体类修改完成之后,我们还需要添加一个配置:
1 2 3 4 5 6 mybatis-plus.global-config.db-config.logic-delete-field =deleted mybatis-plus.global-config.db-config.logic-delete-value =0 mybatis-plus.global-config.db-config.logic-not-delete-value =1
接下来我们来测试
1 2 3 4 5 6 7 8 9 10 11 @SpringBootTest class Springboot01Test { @Autowired private UserDao ud; @Test void contextLoads () { ud.deleteById(1L ); ud.selectList(null ).forEach(System.out::println); } }
测试后我们通过日志发现,原来的 delete 语句变成了现在的 update 语句,而且在执行 selectList 的时候查询不到删除后的数据,MP 已经帮我们全都配置好了
常用注解小总结
注解
作用
@TableName
用来绑定实体类和数据表名称
@TableId
用来设置主键列,可以绑定名称以及修改自动生成类型,例如雪花算法,自增等
@TableField
用来设置非主键列,可以设置绑定名称以及自动填充等等
@Version
用来设置乐观锁,需要配合 OptimisticLockerInterceptor
类使用
@TableLogic
配置逻辑删除,需要在配置文件中添加逻辑删除列,删除和未删除对应的值
高级查询学习
Page 分页查询
我们在使用 mybatis 的时候使用的分页插件是第三方的 PageHelper
分页插件,而在 MP 中为我们内置了一个分页插件,我们只需要将它配置进来就可以使用了
测试分页查询首先要有足够的数据,这里我们通过 for 循环插入30条测试数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @SpringBootTest class Springboot09ApplicationTests { @Autowired private UserDao ud; @Test void contextLoads () { for (int i = 0 ; i < 30 ; i++) { UserEntity user = new UserEntity (); user.setName("伞兵" + i + "号" ); user.setAge(20 +i); user.setEmail("sanbing" + (i+1 ) + "hao@163.com" ); ud.insert(user); } } }
**1.**测试数据插入完毕后,我们开始练习分页查询,首先要引入 MP 的分页插件:
1 2 3 4 5 6 @Bean public PaginationInterceptor paginationInterceptor () { PaginationInterceptor paginationInterceptor = new PaginationInterceptor (); paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize (true )); return paginationInterceptor; }
**2.**配置已经完成了,接下来我们来查询测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @SpringBootTest class Springboot09ApplicationTests { @Autowired private UserDao ud; @Test void contextLoads () { Page<UserEntity> page = new Page <>(2 , 5 ); ud.selectPage(page, null ); System.out.println("======================== Page ========================" ); System.out.println("当前第" + page.getCurrent() + "页" ); System.out.println("总页码为" + page.getPages() + "页" ); System.out.println("结果集为:" + page.getRecords()); System.out.println("查询到" + page.getSize() + "条记录" ); System.out.println("总记录数" + page.getTotal() + "条" ); System.out.println("是否有上一页:" + page.hasPrevious()); System.out.println("是否有下一页:" + page.hasNext()); } }
这样一来分页就测试完成了!
Wrapper 条件构造器
wrapper 条件构造器,它可以帮助我们完成 SQL 中的绝大多数操作,例如大于等于,小于,区间以及模糊查询,对应的条件构造器为 QueryWrapper
,修改操作同样也有对应的条件构造器 UpdateWrapper
接下来我们练习一下:
查询年龄在25-35之间的用户
1 2 3 4 5 6 7 @Test void contextLoads () { QueryWrapper<UserEntity> wrapper = new QueryWrapper <>(); wrapper.between("AGE" , 25 , 35 ); ud.selectList(wrapper).forEach(System.out::println); }
查询名称中包含 ‘伞兵’ 的用户
1 2 3 4 5 6 @Test void contextLoads () { QueryWrapper<UserEntity> wrapper = new QueryWrapper <>(); wrapper.like("NAME" , "伞兵" ); ud.selectList(wrapper).forEach(System.out::println); }
查询所有邮箱为 null 的用户
1 2 3 4 5 6 @Test void contextLoads () { QueryWrapper<UserEntity> wrapper = new QueryWrapper <>(); wrapper.isNull("EMAIL" ); ud.selectList(wrapper).forEach(System.out::println); }
查询名称中不包含 ‘伞兵’ 且年龄大于 20 邮箱不为空的所有用户
1 2 3 4 5 6 7 8 9 @Test void contextLoads () { QueryWrapper<UserEntity> wrapper = new QueryWrapper <>(); wrapper .notLike("NAME" , "伞兵" ) .gt("AGE" , 20 ) .isNotNull("EMAIL" ); ud.selectList(wrapper).forEach(System.out::println); }
映射mapper.xml
开始的时候也说了,MP 是基于 mybatis 框架进行的增强,也就是说除了之前那些功能之外我们还是可以把它当成 mybatis 使用的,可以配置 mapper.xml 映射文件,接下来我们就来配置 xml 开发:
1 2 3 4 5 6 7 8 9 10 11 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-not-delete-value: 1 logic-delete-value: 0 mapper-locations: classpath:mappers/*Mapper.xml
然后在目标位置创建 mapper 文件绑定接口进行调用即可,和 mybatis 是一样的流程