MyBatis-Plus
简介
MyBatis-Plus 是一个 Mybatis 增强版工具,在 MyBatis 上扩充了其他功能没有改变其基本功能,为了简化开发提交效率而存在。
官网文档地址:
https://mp.baomidou.com/guide/
MyBatis-Plus 特性:
https://mp.baomidou.com/guide/#%E7%89%B9%E6%80%A7
Demo:使用 SpringBoot 快速使用 MyBatis-Plus
(1)准备工作
需要 Java 开发环境(JDK)以及相应的开发工具(IDE)。
需要 maven(用来下载相关依赖的 jar 包)。
需要 SpringBoot。
可以使用 IDEA 安装一个 mybatis-plus 插件。
(2)创建一个 SpringBoot 项目。
方式一:去官网 https://start.spring.io/ 初始化一个,然后导入 IDE 工具即可。
方式二:直接使用 IDE 工具创建一个。 Spring Initializer。
(3)添加 MyBatis-Plus 依赖(mybatis-plus-boot-starter)
1 | <dependency> |
(4)为了测试开发,此处使用 mysql 8,需要引入 mysql 相关依赖。
为了简化代码,引入 lombok 依赖(减少 getter、setter 等方法)。
1 | <dependency> |
(5)完整依赖文件(pom.xml)
1 | <?xml version="1.0" encoding="UTF-8"?> |
(6)使用一个表进行测试。
仅供参考,可以定义 创建时间、修改时间等字段。
1 | DROP DATABASE IF EXISTS testMyBatisPlus; |
(7)在 application.yml 文件中配置 mysql 数据源信息。
1 | spring: |
(8)编写表对应的 实体类。
1 | package entity; |
(9)编写操作实体类的 Mapper 类。
直接继承 BaseMapper,这是 mybatis-plus 封装好的类。
1 | package mapper; |
(10)实体类、Mapper 类都写好了,就可以使用了。
Step1:先得在启动类里扫描 Mapper 类,即添加 @MapperScan 注解
1 | package com.lyh.test.testmybatisplus; |
Step2:写一个测试类测试一下。
1 | package com.lyh.test.testmybatisplus; |
(11)总结:
通过以上简单操作,就能对 user 表进行 CRUD 操作,不需要去编写 xml 文件。
注:
若遇到 @Autowired 标记的变量出现 红色下划线,但是不影响 正常运行。
可以进入 Settings,找到 Inspection,并选择其中的 Spring Core -> Code -> Autowiring for Bean Class,将 Error 改为 Warning,即可。
创建数据库表
创建user表
1 | DROP TABLE IF EXISTS user; |
注意:真实开发中往往都会有这四个字段,version(乐观锁)、deleted(逻辑删除)、gmt_create(创建时间)、gmt_modified(修改时间)
导入依赖
1 | <!-- 数据库驱动 --> |
注意:尽量不要同时导入 mybatis 和 mybatis-plus!避免版本的差异造成无法预知的问题。
连接数据库
创建application.yml
1 | spring: |
业务代码
实体类
1 |
|
mapper接口
1 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
注意:我们需要在主启动类Application上去扫描我们的mapper包下的所有接口 @MapperScan(“com.kwhua.mapper”)
测试
1 |
|
所有数据输出
配置日志
我们所有的sql现在是不可见的,我们希望知道它是怎么执行的,所有我们要配置日志的输出 application.yml文件添加日志配置
1 | #配置日志 |
查看执行sql的日志信息
Mybatis-plus的mapper层CRUD
service层:需继承 IService<实体类>
mapper层:需继承BaseMapper<实体类>
model层 :需继承Model<实体类>,且对应mapper层需继承BaseMapper<实体类>,然后就可以让model层调用CRUD方法。 CRUD方法与mapper层方法一样
插入操作
1 | // 测试插入 |
看到id会自动填充。数据库插入的id的默认值为:全局的唯一id
主键生成策略
1)主键自增
1、实体类字段上 @TableId(type = IdType.AUTO)
2、数据库id字段设置为自增!
3、再次测试(可以看到id值比上次插入的大1)id的生成策略源码解释
1 | public enum IdType { |
更新操作
1 |
|
自动填充
创建时间、修改时间!这两个字段操作都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表都要配置上gmt_create、gmt_modified!而且需要自动化!
方式一:数据库级别(工作中一般不用)
1、在表中新增字段 gmt_create, gmt_modified
2、把实体类同步
1 | private Date gmtCreate; |
3、再次查看
方式二:代码级别
1、删除数据库的默认值、更新操作!
2、实体类字段属性上需要增加注解
1 | // 字段添加填充内容 |
3、编写处理器来处理这个注解即可!
1 |
|
4、测试插入和更新,检查时间变化。
乐观锁
乐观锁 : 顾名思义,十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题, 再次更新值测试
悲观锁:顾名思义,十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
乐观锁实现方式:
取出记录时,获取当前version 。更新时,带上这个version 执行更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败
乐观锁:
1、先查询,获得版本号 version = 1
1 | -- A |
乐观锁测试
1、给数据库中增加version字段!
2、实体类加对应的字段
1 | //乐观锁Version注解 |
3、注册组件
1 | // 扫描我们的 mapper 文件夹 |
4、测试
1 | // 测试乐观锁成功! |
version字段已经由1变成了2
1 | // 测试乐观锁失败!多线程下 |
可以看到线程1执行更新失败
查询操作
1 | // 测试查询 |
分页查询
官方文档说明:https://mp.baomidou.com/guide/page.html
mybatis的一级缓存和二级缓存
一级缓存不跨sqlsession,不同的sqlsession中的一级缓存不同,每次查询的时候先去一级缓存中查,若没有再到数据库查询,然后存入一级缓存,若执行了commit操作,一级缓存则被清空,设计成会清空是为了防止脏读,一级缓存不需要在.xml中进行设置,默认开启
正式开发时,事务由spring管理,发生在service中,一个service中包括很多mapper方法的调用,当事务开启时,创建一个sqlsession,事务结束时,关闭sqlsession,所有一个service可以使用1级缓存
二级缓存,默认不开启,每一个namespace有一个二级缓存区域,如果两个mapper的namespace相同,它们的二级缓存区域是同一个。执行commit也会清空对应的二级缓存
二级缓存的开启,在sqlMapConfig.xml和对应的mapper.xml中都需要进行配置
yml文件配置
1 | mybatis-plus: |
在mapper.xml中的配置,写一个
禁用/开启二级缓存
二级缓存的存储介质不固定,可以是内存也可以是硬盘,或者是远程服务器,所以要求pojo实现序列化接口
session不关闭,数据写不到二级缓存区域
项目里分页实现的代码:
1.先配置文件
1 | //创建一个自定义的配置类 MybatisPlusConfig |
2.再直接使用 controller层
1 | public R getRechargeRecord( Integer page)throws UserNotExistException { |
3.service层
1 | public R getRechargeRecord(Integer currentPage) throws UserNotExistException { |
4.定义的vo
1 |
|
5.定义的mapper和xml代码
1 | List<TransactionDTO> listAccountTransactionByCompanyId(IPage<TransactionDTO> page); |
删除操作
物理删除
1 | // 测试删除 |
逻辑删除
物理删除 :从数据库中直接移除
逻辑删除 :在数据库中没有被移除,而是通过一个变量来让它失效!deleted = 0 => deleted = 1 管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
说明:
只对自动注入的sql起效:
- 插入: 不作限制
- 查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 更新: 追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 删除: 转变为 更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
) - 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
使用方法:
步骤1: 在数据表中增加一个 deleted 字段
步骤2: 实体类中加上@TableLogic
注解
1 | //逻辑删除 |
步骤3:mapper.xml加一个属性(可选)
步骤4:配置删除组件
1 | // 逻辑删除组件 |
配置com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig
例: application.yml
1 | mybatis-plus: |
步骤5:测试删除
删除后测试查询:字段值从0修改成了1
常见问题:
- 如何 insert ?
- 字段在数据库定义默认值(推荐)
- insert 前自己 set 值
- 使用自动填充功能
- 删除接口自动填充功能失效
- 使用
update
方法并:UpdateWrapper.set(column, value)
(推荐)- 使用
update
方法并:UpdateWrapper.setSql("column=value")
- 使用Sql注入器注入
com.baomidou.mybatisplus.extension.injector.methods.LogicDeleteByIdWithFill
并使用(推荐)
截断表
清空某个记录表的数据时,可以用到截断:truncate 这样会使id从1开始增长,但是会使数据全部丢失,binlog日志也没有记录
网上查到的利用mybatis-plus的方法:(自己的项目一直报错)
1.mapper接口里
1 | public interface userInfoMapper extends BaseMapper<UserInfo> { |
2.mapper的xml文件里
1 | public interface userInfoMapper extends BaseMapper<UserInfo> { |
3.直接使用自带的remove接口 ,同时使用QueryWrapper参数
1 | userInfoTempService.remove(new QueryWrapper<>()) |
自己的项目里成功的方法:可以调用 helmetOnOffMapper.deleteTable(“helmet_on_off”); 更改表名删除对应表
1 | public interface HelmetOnOffMapper extends BaseMapper<HelmetOnOff> { |
性能分析插件
作用:性能分析拦截器,用于输出每条 SQL 语句及其执行时间
MP也提供性能分析插件,如果超过这个时间就停止运行!
1、导入插件
1 | /** |
条件构造器(Wrapper)
isNotNull :非空
ge:大于等于 le:小于等于
gt:大于 lt:小于
eq:等于 (用的最多)
例子:
1 |
|
1 |
|
代码自动生成器
AutoGenerator 简介
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
与 mybatis 中的 mybatis-generator-core 类似。
1.添加依赖
MyBatisPlus 在3.0.3版本之前使用代码生成器因为存在默认依赖,所以不需要手动添加其他的依赖
项目中使用的是3.4.0的版本,所以需要手动添加依赖
1 | <!-- mybatis-plus begain =================================== --> |
MP默认将Velocity作为模板引擎,同时也支持Freemarker、Beetl
2.新建一个java类,在类中写方法
1 | import com.baomidou.mybatisplus.annotation.IdType; |
3.执行主方法即可生成对应代码
注:
1.上述方法自动生成的mapper类里没有mapper注入,需要在application的入口方法所在的类上加 @MapperScan("mapper所在的包")
2.上述方法也会将数据库内的视图表生成,注意不用时可以删除
扩展:这种全局性的注解一般都加在Application类的上面。
3.在使用mybatis-generator时发现数据库类型为tinyint(1)时,映射生成的字段为Boolean类型,是由于tinyint(1)时默认为Boolean型,所以便将tinyint类型设置为tinyint(4)。但是情况是映射过来后的数据类型为byte型。
所以需要进行手动配置相关的类型转换组件,使用 Integer 作为tinyint的Java类型
- tinyint(1) 转换成数字类型而不是布尔类型
- tinyint(n) n>1 转换成 Integer 而非 Byte
生成的实体类的注解的备注:
注解说明 | 值 | 描述 |
---|---|---|
表名注解 @TableName com.baomidou.mybatisplus.annotations.TableName |
value resultMap |
表名( 默认空 ) xml 字段映射 resultMap ID |
主键注解 @TableId com.baomidou.mybatisplus.annotations.TableId |
value type |
字段值(驼峰命名方式,该值可无) 主键 ID 策略类型( 默认 INPUT ,全局开启的是 ID_WORKER ) 暂不支持组合主键 |
字段注解 @TableField com.baomidou.mybatisplus.annotations.TableField |
value el exist strategy |
字段值(驼峰命名方式,该值可无) 详看注释说明 是否为数据库表字段( 默认 true 存在,false 不存在 ) 字段验证 ( 默认 非 null 判断,查看 com.baomidou.mybatisplus.enums.FieldStrategy ) |
序列主键策略注解 @KeySequence com.baomidou.mybatisplus.annotations.KeySequence |
value clazz |
序列名 id的类型 |
乐观锁标记注解 @Version com.baomidou.mybatisplus.annotations.Version |
主键填充策略 TableId
IdType.AUTO ———-数据库ID自增
IdType.INPUT ———-用户输入ID
IdType.ID_WORKER———- 全局唯一ID,内容为空自动填充(默认配置)这是MP在Sequence的基础上进行部分优化,用于产生全局唯一ID
IdType.UUID ———-全局唯一ID,内容为空自动填充
字段填充策略 FieldFill
DEFAULT ———-默认不处理
INSERT ———-插入填充字段
UPDATE ———-更新填充字段
INSERT_UPDATE ———-插入和更新填充字段
在MP中,我们建议数据库表名采用下划线命名方式,而表字段名采用驼峰命名方式。
这么做的原因是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。当然如果项目里不用考虑这点性能损耗,那么你采用下滑线也是没问题的,只需要在生成代码时配置dbColumnUnderline属性就可以。
统一返回结果集
com.baomidou.mybatisplus.extension.api
统一返回结果集:R
二、Mybatis-Plus 常用操作
1、配置日志
1 | 【参考地址(两种方式配置日志)】 |
想要查看执行的 sql 语句,可以在 yml 文件中添加配置信息,如下。
1 | mybatis-plus: |
如下图所示:执行时会打印出 sql 语句。
2、简单认识一下常用注解
1 | 【@TableName 】 |
4、自动填充数据功能
(1)简介
添加、修改数据时,每次都会使用相同的方式进行填充。比如 数据的创建时间、修改时间等。
Mybatis-plus 支持自动填充这些字段的数据。
给之前的数据表新增两个字段:创建时间、修改时间。
1 | CREATE TABLE test_mybatis_plus_user |
并使用 代码生成器生成代码。
(2)未使用自动填充时
未使用 自动填充时,每次添加、修改数据都可以手动对其进行添加。
1 | @SpringBootTest |
(3)使用自动填充功能。
Step1:
使用 @TableField 注解,标注需要进行填充的字段。
1 | /** |
Step2:
自定义一个类,实现 MetaObjectHandler 接口,并重写方法。
添加 @Component 注解,交给 Spring 去管理。
1 | package com.lyh.test.test_mybatis_plus.handler; |
Step3:
简单测试一下。
1 | @Test |
5、逻辑删除
(1)简介
删除数据,可以通过物理删除,也可以通过逻辑删除。
物理删除指的是直接将数据从数据库中删除,不保留。
逻辑删除指的是修改数据的某个字段,使其表示为已删除状态,而非删除数据,保留该数据在数据库中,但是查询时不显示该数据(查询时过滤掉该数据)。
给数据表增加一个字段:delete_flag,用于表示该数据是否被逻辑删除。
1 | CREATE TABLE test_mybatis_plus_user |
(2)使用逻辑删除。
可以定义一个自动填充规则,初始值为 0。0 表示未删除, 1 表示删除。
1 | /** |
(3)简单测试
使用 mybatis-plus 封装好的方法时,会自动添加逻辑删除的功能。
若是自定义的 sql 语句,需要手动添加逻辑。
1 | @Test |
现有数据
执行 testDelete 进行逻辑删除。
若去除 TableLogic 注解,再执行 testDelete 时进行物理删除,直接删除这条数据。
6、分页插件的使用
(1)简介
与 mybatis 的插件 pagehelper 用法类似。
通过简单的配置即可使用。
(2)使用
Step1:
配置分页插件。
编写一个 配置类,内部使用 @Bean 注解将 PaginationInterceptor 交给 Spring 容器管理。
1 | package com.lyh.test.test_mybatis_plus.config; |
Step2:
编写分页代码。
直接 new 一个 Page 对象,对象需要传递两个参数(当前页,每页显示的条数)。
调用 mybatis-plus 提供的分页查询方法,其会将 分页查询的数据封装到 Page 对象中。
1 | @Test |
7、乐观锁的实现
(1)首先认识一下 读问题、写问题?
操作数据库数据时,遇到的最基本问题就是 读问题与写问题。
读问题 指的是从数据库中读取数据时遇到的问题,比如:脏读、幻读、不可重复读。
1 | 【脏读、幻读、不可重复读 参考地址:】 |
写问题 指的是数据写入数据库时遇到的问题,比如:丢失更新(多个线程同时对某条数据更新,无论执行顺序如何,都会丢失其他线程更新的数据)
(2)如何解决写问题?
乐观锁、悲观锁就是为了解决 写问题而存在的。
乐观锁:总是假设最好的情况,每次读取数据时认为数据不会被修改(即不加锁),当进行更新操作时,会判断这条数据是否被修改,未被修改,则进行更新操作。若被修改,则数据更新失败,可以对数据进行重试(重新尝试修改数据)。
悲观锁:总是假设最坏的情况,每次读取数据时认为数据会被修改(即加锁),当进行更新操作时,直接更新数据,结束操作后释放锁(此处才可以被其他线程读取)。
(3)乐观锁、悲观锁使用场景?
乐观锁一般用于读比较多的场合,尽量减少加锁的开销。
悲观锁一般用于写比较多的场合,尽量减少 类似 乐观锁重试更新引起的性能开销。
(4)乐观锁两种实现方式
方式一:通过版本号机制实现。
在数据表中增加一个 version 字段。
取数据时,获取该字段,更新时以该字段为条件进行处理(即set version = newVersion where version = oldVersion),若 version 相同,则更新成功(给新 version 赋一个值,一般加 1)。若 version 不同,则更新失败,可以重新尝试更新操作。
方式二:通过 CAS 算法实现。
CAS 为 Compare And Swap 的缩写,即比较交换,是一种无锁算法(即在不加锁的情况实现多线程之间的变量同步)。
CAS 操作包含三个操作数 —— 内存值(V)、预期原值(A)和新值(B)。如果内存地址里面的值 V 和 A 的值是一样的,那么就将内存里面的值更新成B。若 V 与 A 不一致,则不执行任何操作(可以通过自旋操作,不断尝试修改数据直至成功修改)。即 V == A ? V = B : V = V。
CAS 可能导致 ABA 问题(两次读取数据时值相同,但不确定值是否被修改过),比如两个线程操作同一个变量,线程 A、线程B 初始读取数据均为 A,后来 线程B 将数据修改为 B,然后又修改为 A,此时线程 A 再次读取到的数据依旧是 A,虽然值相同但是中间被修改过,这就是 ABA 问题。可以加一个额外的标志位 C,用于表示数据是否被修改。当标志位 C 与预期标志位相同、且 V == A 时,则更新值 B。
(5)mybatis-plus 实现乐观锁(通过 version 机制)
实现思路:
Step1:取出记录时,获取当前version
Step2:更新时,带上这个version
Step3:执行更新时, set version = newVersion where version = oldVersion
Step4:如果version不对,就更新失败
(6)mybatis-plus 代码实现乐观锁
Step1:
配置乐观锁插件。
编写一个配置类(可以与上例的分页插件共用一个配置类),将 OptimisticLockerInterceptor 通过 @Bean 交给 Spring 管理。
1 | /** |
Step2:
定义一个数据库字段 version。
1 | CREATE TABLE test_mybatis_plus_user |
Step3:
使用 @Version 注解标注对应的实体类。
可以通过 @TableField 进行数据自动填充。
1 | /** |
Step4:
简单测试一下,可以看一下 控制台 打印的 sql 语句。
1 | @Test |
三、Mybatis-Plus CRUD 操作简单了解一下
1、Mapper 接口方法(CRUD)简单了解一下
(1)简介
使用代码生成器生成的 mapper 接口中,其继承了 BaseMapper 接口。
而 BaseMapper 接口中封装了一系列 CRUD 常用操作,可以直接使用,而不用自定义 xml 与 sql 语句进行 CRUD 操作(当然根据实际开发需要,自定义 sql 还是有必要的)。
此处简单介绍一下 BaseMapper 接口中的常用方法。
(2)方法介绍
混个眼熟,用多了就记得了。
1 | 【添加数据:(增)】 |
1.selectById的问题
表的主键列名不是id时:查询不到数据,因为Mybatisplus自动生成的sql语句where后面拼接的是where null = ?
这就表示表的主键列名的名字不是id,而Mybatisplus默认的是使用id为主键名的
解决方法:
1 | @Id |
1 | selectById和selectOne的区别: |
2.insert的问题
设置了@TableId(“specifications_id”)以后,并且数据库的主键列是自增的类型而不是我们手段插入的数据,那么使用Mybaitsplus自带的insert方法时,就会导致数据存不进数据库
1 | @Id |
2、Service 接口方法(CRUD)简单了解一下
(1)简介
使用 代码生成器 生成的 service 接口中,其继承了 IService 接口。
IService 内部进一步封装了 BaseMapper 接口的方法(当然也提供了更详细的方法)。
使用时,可以通过 生成的 mapper 类进行 CRUD 操作,也可以通过 生成的 service 的实现类进行 CRUD 操作。(当然,自定义代码执行也可)
此处简单介绍一下 IService 中封装的常用方法。
(2)方法介绍
混个眼熟,用多了就记得了。
内部封装了 BaseMapper 的方法,也提供了新的方法。
比如:
添加了 批量更新 方法、更新或修改方法等。
对 查询方法 做了细化,使用 get 命名的方法查询一条数据,使用 list 命名的方法查询多条数据等。
增加了链式调用的方法。
1 | 【添加数据:(增)】 |
3、条件构造器(Wrapper,定义 where 条件)
(1)简介
上面介绍的 接口方法的参数中,会出现各种 wrapper,比如 queryWrapper、updateWrapper 等。wrapper 的作用就是用于定义各种各样的查询条件(where)。
1 | Wrapper 条件构造抽象类 |
(2)常用条件
参考源码以及官方文档总结的,还是一句话,混个眼熟,多用用就熟练了。
1 | 【通用条件:】 |
(3)简单使用,测试一下 QueryWrapper
1 | @Test |
当前数据库数据如下:
执行测试方法输出如下:
lombok包
1、idea下安装lombok插件
[settings/plugins在插件库中搜索安装即可。]
(https://img-blog.csdnimg.cn/20190501112839630.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Jlbnh0MDUwOA==,size_16,color_FFFFFF,t_70)
2、重启idea
3、在maven库中添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
4、在实体类上添加注解即可生效
@Data
1、@Data可以为类提供读写功能,从而不用写get、set方法。
2、他还会为类提供 equals()、hashCode()、toString() 方法。
@SneakyThrows(隐秘抛出)
是lombok包下的注解 并且继承了Throwable作用 是为了用try{}catch{}捕捉异常添加之后会在 代码编译时 自动捕获异常
二、spring整合mybatis-plus:
正如官方所说,mybatis-plus在mybatis的基础上只做增强不做改变,因此其与spring的整合亦非常简单。只需把mybatis的依赖换成mybatis-plus的依赖,再把sqlSessionFactory换成mybatis-plus的即可。接下来看具体操作:
1、pom.xml:
核心依赖如下:
1 | <!-- spring --> |
注意:这些是核心依赖,本项目还用到了mysql驱动、c3p0、日志(slf4j-api,slf4j-log4j2)、lombok。集成mybatis-plus要把mybatis、mybatis-spring去掉,避免冲突;lombok是一个工具,添加了这个依赖,开发工具再安装Lombok插件,就可以使用它了,最常用的用法就是在实体类中使用它的@Data注解,这样实体类就不用写set、get、toString等方法了。关于Lombok的更多用法,请自行百度。
2、log4j.xml:
1 |
|
3、jdbc.properties:
1 | jdbc.driver=com.mysql.jdbc.Driver |
4、mybatis-config.xml:
1 |
|
注:因为是与spring整合,所有mybatis-plus的大部分都写在spring的配置文件中,这里定义一个空的mybatis-config.xml即可。
5、spring-dao.xml:
1 |
|
6、entity:
1 |
|
7、mapper:
1 | public interface EmplopyeeDao extends BaseMapper<Employee> { |
这样就完成了mybatis-plus与spring的整合。首先是把mybatis和mybatis-spring依赖换成mybatis-plus的依赖,然后把sqlsessionfactory换成mybatis-plus的,然后实体类中添加@TableName
、@TableId
等注解,最后mapper继承BaseMapper
即可。
8、测试:
1 |
|
运行该junit,可输出获取到的连接,说明整合没问题:
image.png
本文所有代码本人均亲自测试过,本文涉及代码又较多,为了不影响篇幅,故非必要处不再截图。接下来的所有操作都是基于此整合好的项目。
三、mp的通用crud:
需求:
存在一张 tb_employee 表,且已有对应的实体类 Employee,实现tb_employee 表的 CRUD 操作我们需要做什么呢?
基于 Mybatis:
需要编写 EmployeeMapper 接口,并在 EmployeeMapper.xml 映射文件中手动编写 CRUD 方法对应的sql语句。
基于 MP:
只需要创建 EmployeeMapper 接口, 并继承 BaseMapper 接口。
我们已经有了Employee、tb_employee了,并且EmployeeDao也继承了BaseMapper了,接下来就使用crud方法。
1、insert操作:
1 |
|
执行添加操作,直接调用insert方法传入实体即可。
2、update操作:
1 |
|
注:注意这两个update操作的区别,updateById
方法,没有传值的字段不会进行更新,比如只传入了lastName,那么age、gender等属性就会保留原来的值;updateAllColumnById
方法,顾名思义,会更新所有的列,没有传值的列会更新为null。
3、select操作:
(1)、根据id查询:
1 | Employee employee = emplopyeeDao.selectById(1); |
(2)、根据条件查询一条数据:
1 | Employee employeeCondition = new Employee(); |
注:这个方法的sql语句就是where id = 1 and last_name = 更新测试
,若是符合这个条件的记录不止一条,那么就会报错。
(3)、根据查询条件返回多条数据:
当符合指定条件的记录数有多条时,上面那个方法就会报错,就应该用这个方法。
1 | Map<String,Object> columnMap = new HashMap<>(); |
注:查询条件用map集合封装,columnMap,写的是数据表中的列名,而非实体类的属性名。比如属性名为lastName,数据表中字段为last_name,这里应该写的是last_name。selectByMap方法返回值用list集合接收。
(4)、通过id批量查询:
1 | List<Integer> idList = new ArrayList<>(); |
注:把需要查询的id都add到list集合中,然后调用selectBatchIds方法,传入该list集合即可,该方法返回的是对应id的所有记录,所有返回值也是用list接收。
(5)、分页查询:
1 | List<Employee> employees = emplopyeeDao.selectPage(new Page<>(1,2),null); |
注:selectPage方法就是分页查询,在page中传入分页信息,后者为null的分页条件,这里先让其为null,讲了条件构造器再说其用法。这个分页其实并不是物理分页,而是内存分页。也就是说,查询的时候并没有limit语句。等配置了分页插件后才可以实现真正的分页。
4、delete操作:
(1)、根据id删除:
1 | emplopyeeDao.deleteById(1); |
(2)、根据条件删除:
1 | Map<String,Object> columnMap = new HashMap<>(); |
注:该方法与selectByMap类似,将条件封装在columnMap中,然后调用deleteByMap方法,传入columnMap即可,返回值是Integer类型,表示影响的行数。
(3)、根据id批量删除:
1 | List<Integer> idList = new ArrayList<>(); |
注:该方法和selectBatchIds类似,把需要删除的记录的id装进idList,然后调用deleteBatchIds,传入idList即可。
四、全局策略配置:
通过上面的小案例我们可以发现,实体类需要加@TableName注解指定数据库表名,通过@TableId注解指定id的增长策略。实体类少倒也无所谓,实体类一多的话也麻烦。所以可以在spring-dao.xml的文件中进行全局策略配置。
1 | <!-- 5、mybatisplus的全局策略配置 --> |
这里配置了还没用,还需要在sqlSessionFactory中注入配置才会生效。如下:
1 | <!-- 3、配置mybatisplus的sqlSessionFactory --> |
如此一来,实体类中的@TableName注解和@TableId注解就可以去掉了。
五、条件构造器(EntityWrapper):
以上基本的 CRUD 操作,我们仅仅需要继承一个 BaseMapper 即可实现大部分单表 CRUD 操作。BaseMapper 提供了多达 17 个方法供使用, 可以极其方便的实现单一、批量、分页等操作,极大的减少开发负担。但是mybatis-plus的强大不限于此,请看如下需求该如何处理:
需求:
我们需要分页查询 tb_employee 表中,年龄在 18~50 之间性别为男且姓名为 xx 的所有用户,这时候我们该如何实现上述需求呢?
使用MyBatis : 需要在 SQL 映射文件中编写带条件查询的 SQL,并用PageHelper 插件完成分页. 实现以上一个简单的需求,往往需要我们做很多重复单调的工作。
使用MP: 依旧不用编写 SQL 语句,MP 提供了功能强大的条件构造器 —— EntityWrapper。
接下来就直接看几个案例体会EntityWrapper的使用。
1、分页查询年龄在18 - 50且gender为0、姓名为tom的用户:
1 | List<Employee> employees = emplopyeeDao.selectPage(new Page<Employee>(1,3), |
注:由此案例可知,分页查询和之前一样,new 一个page对象传入分页信息即可。至于分页条件,new 一个EntityWrapper对象,调用该对象的相关方法即可。between方法三个参数,分别是column、value1、value2,该方法表示column的值要在value1和value2之间;eq是equals的简写,该方法两个参数,column和value,表示column的值和value要相等。注意column是数据表对应的字段,而非实体类属性字段。
2、查询gender为0且名字中带有老师、或者邮箱中带有a的用户:
1 | List<Employee> employees = emplopyeeDao.selectList( |
注:未说分页查询,所以用selectList即可,用EntityWrapper的like方法进行模糊查询,like方法就是指column的值包含value值,此处like方法就是查询last_name中包含“老师”字样的记录;“或者”用or或者orNew方法表示,这两个方法区别不大,用哪个都可以,可以通过控制台的sql语句自行感受其区别。
3、查询gender为0,根据age排序,简单分页:
1 | List<Employee> employees = emplopyeeDao.selectList( |
注:简单分页是指不用page对象进行分页。orderBy方法就是根据传入的column进行升序排序,若要降序,可以使用orderByDesc方法,也可以如案例中所示用last方法;last方法就是将last方法里面的value值追加到sql语句的后面,在该案例中,最后的sql语句就变为select ······ order by desc limit 1, 3
,追加了desc limit 1,3
所以可以进行降序排序和分页。
4、分页查询年龄在18 - 50且gender为0、姓名为tom的用户:
条件构造器除了EntityWrapper,还有Condition。用Condition来处理一下这个需求:
1 | List<Employee> employees = emplopyeeDao.selectPage( |
注:Condition和EntityWrapper的区别就是,创建条件构造器时,EntityWrapper是new出来的,而Condition是调create方法创建出来。
5、根据条件更新:
1 |
|
注:该案例表示把last_name为tom,age为25的所有用户的信息更新为employee中设置的信息。
6、根据条件删除:
1 | emplopyeeDao.delete( |
注:该案例表示把last_name为tom、age为16的所有用户删除。
Mybatis-Plus和Mybatis的区别
区别一
如果Mybatis Plus是扳手,那Mybatis Generator就是生产扳手的工厂。
通俗来讲——
MyBatis:一种操作数据库的框架,提供一种Mapper类,支持让你用java代码进行增删改查的数据库操作,省去了每次都要手写sql语句的麻烦。但是!有一个前提,你得先在xml中写好sql语句,是不是很麻烦?于是有下面的↓
Mybatis Generator:自动为Mybatis生成简单的增删改查sql语句的工具,省去一大票时间,两者配合使用,开发速度快到飞起。至于标题说的↓
Mybatis Plus:国人团队苞米豆在Mybatis的基础上开发的框架,在Mybatis基础上扩展了许多功能,荣获了2018最受欢迎国产开源软件第5名,当然也有配套的↓
Mybatis Plus Generator:同样为苞米豆开发,比Mybatis Generator更加强大,支持功能更多,自动生成Entity、Mapper、Service、Controller等
总结:
数据库框架:Mybatis Plus > Mybatis
代码生成器:Mybatis Plus Generator > Mybatis Generator
区别二
Mybatis-Plus是一个Mybatis的增强工具,它在Mybatis的基础上做了增强,却不做改变。我们在使用Mybatis-Plus之后既可以使用Mybatis-Plus的特有功能,又能够正常使用Mybatis的原生功能。Mybatis-Plus(以下简称MP)是为简化开发、提高开发效率而生,但它也提供了一些很有意思的插件,比如SQL性能监控、乐观锁、执行分析等。
Mybatis虽然已经给我们提供了很大的方便,但它还是有不足之处,实际上没有什么东西是完美的,MP的存在就是为了稍稍弥补Mybatis的不足。在我们使用Mybatis时会发现,每当要写一个业务逻辑的时候都要在DAO层写一个方法,再对应一个SQL,即使是简单的条件查询、即使仅仅改变了一个条件都要在DAO层新增一个方法,针对这个问题,MP就提供了一个很好的解决方案,之后我会进行介绍。另外,MP的代码生成器也是一个很有意思的东西,它可以让我们避免许多重复性的工作,下面我将介绍如何在你的项目中集成MP。
mybatis-plus添加依赖
注意这里的依赖!springboot项目和spring项目的依赖不一样!
首先添加maven依赖:springboot项目用下面这个依赖
1 | <dependency> |
如果是spring项目则用下面的依赖
1 | <dependency> |
然后就可以使用BaseMapper <entity范型>
启动类上还需要加上 @MapperScan(“UserMapper接口所在的包”)
例如:@MapperScan(“top.yumbo.dao”) 这样启动的时候就不会因为注入的时候导致报错
一、 集成步骤:(首先,你要有个spring项目)
集成依赖,pom中加入依赖即可,不多说:
1 | <!-- mybatis mybatis-plus mybatis-spring mvc --> |
说明:笔者使用的版本为:mybatis-plus.version=2.1-gamma,上边的代码中有两个依赖,第一个是mybatis-plus核心依赖,第二个是使用代码生成器时需要的模板引擎依赖,若果你不打算使用代码生成器,此处可不引入。
注意:mybatis-plus的核心jar包中已集成了mybatis和mybatis-spring,所以为避免冲突,请勿再次引用这两个jar包。
说明:笔者使用的版本为:mybatis-plus.version=2.1-gamma,上边的代码中有两个依赖,第一个是mybatis-plus核心依赖,第二个是使用代码生成器时需要的模板引擎依赖,若果你不打算使用代码生成器,此处可不引入。
注意:mybatis-plus的核心jar包中已集成了mybatis和mybatis-spring,所以为避免冲突,请勿再次引用这两个jar包。
二、 在spring中配置MP:
1 | <bean id="sqlSessionFactory" |
1 | <bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration"> |
注意:只要做如上配置就可以正常使用mybatis了,不要重复配置。MP的配置和mybatis一样,都是配置一个sqlSessionFactory,只是现在所配置的类在原本的SqlSessionFactoryBean基础上做了增强。插件等配置请按需取舍。
插件配置,按需求配置就可以,此处把可以配置的插件都列了出来,具体的请看代码注释:
1 | <configuration> |
注意:执行分析拦截器和性能分析推荐只在开发时调试程序使用,为保证程序性能和稳定性,建议在生产环境中注释掉这两个插件。
数据源:(此处使用druid)
1 | <!-- 配置数据源 --> |
到此,MP已经集成进我们的项目中了,下面将介绍它是如何简化我们的开发的。
三、 简单的CURD操作:
假设我们有一张user表,且已经建立好了一个与此表对应的实体类User,我们来介绍对user的简单增删改查操作。
建立DAO层接口。我们在使用普通的mybatis时会建立一个DAO层接口,并对应一个xml用来写SQL。在这里我们同样要建立一个DAO层接口,但是若无必要,我们甚至不需要建立xml,就可以进行资源的CURD操作了,我们只需要让我们建立的DAO继承MP提供的BaseMapper<?>即可:
1 | public interface UserMapper extends BaseMapper { } |
1 | 多条件分页查询: |
下边这个,多条件构造器。其实对于条件过于复杂的查询,还是建议使用原生mybatis的方式实现,易于维护且逻辑清晰,如果所有的数据操作都强行使用MP,就失去了MP简化开发的意义了。所以在使用时请按实际情况取舍,在这里还是先介绍一下。
1 | public Page<T> selectPage(Page<T> page, EntityWrapper<T> entityWrapper) { |
1 | 条件构造一(上边方法的entityWrapper参数): |
1 | 条件构造二(同上): |
1 | //实体类 |
下边就是CURD操作了:
1 | // 初始化 成功标识 |
就是这样了,可能你会说MP封装的有些过分了,这样做会分散数据逻辑到不同的层面中,难以管理,使代码难以理解。其实确实是这样,这就需要你在使用的时候注意一下了,在简化开发的同时也要保证你的代码层次清晰,做一个战略上的设计或者做一个取舍与平衡。
总结
MP的宗旨是简化开发,但是它在提供方便的同时却容易造成代码层次混乱,我们可能会把大量数据逻辑写到service层甚至contoller层中,使代码难以阅读。凡事过犹不及,在使用MP时一定要做分析,不要将所有数据操作都交给MP去实现。毕竟MP只是mybatis的增强工具,它并没有侵入mybatis的原生功能,在使用MP的增强功能的同时,原生mybatis的功能依然是可以正常使用的
一些疑问
mapper层可以完成的数据操作,为什么还需要设计services层也进行数据操作,造成分层结构不明确?
在后端开发过程中,用到mybatis-plus,发现在其内部存在着两种数据库操作接口,Iservice和BaseMapper,如果只是用增删改查会发现两者的功能是一致的,除了方法名称有所不同,其他的基本相似。
先演示一下基本开发中的继承关系,手动创建的Service继承于ServiceImpl,并加载自己创建的Mapper
1 |
|
1 | @Mapper |
如上,就是一般开发的基本模版代码,足以满足各种需求功能
先看一下继承结构
我们继承的ServiceImpl依旧实现了BaseMapper接口和Iservice接口,明明我们单独写了AdminMapper,并且继承了BaseMapper,现在ServiceImpl还是实现了BaseMapper,那我直接一个Service用下来不就行了,创建两套类,功能相似,还容易混乱,代码结构冗余。
对比一下两个接口的方法
Service简直是BaseMapper的大扩充,不但包含了所有基本方法,还加入了很多批处理功能
Service CRUD 接口
说明:
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用:
get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆
泛型 T 为任意实体对象
建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
对象 Wrapper 为 条件构造器
Mapper CRUD 接口
说明:
通用 CRUD 封装BaseMapper接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
泛型 T 为任意实体对象
参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
对象 Wrapper 为 条件构造器
Service虽然加入了数据库的操作,但还是以业务功能为主,而更加复杂的SQL查询,还是要靠Mapper对应的XML文件里去编写SQL语句