一、简介

官网:http://mp.baomidou.com/
参考教程:http://mp.baomidou.com/guide/

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

二、特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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 注入攻击

    三、创建数据库并初始化

    3.1、创建数据库mybatis_plus

    1. create database `mybatis_plus`;

    3.2、创建User表

    1. DROP TABLE IF EXISTS user;
    2. CREATE TABLE user
    3. (
    4. id BIGINT(20) NOT NULL COMMENT '主键ID',
    5. name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    6. age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    7. email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    8. PRIMARY KEY (id)
    9. );

    3.3、对User表进行初始化

    1. DELETE FROM user;
    2. INSERT INTO user (id, name, age, email) VALUES
    3. (1, 'Jone', 18, 'test1@baomidou.com'),
    4. (2, 'Jack', 20, 'test2@baomidou.com'),
    5. (3, 'Tom', 28, 'test3@baomidou.com'),
    6. (4, 'Sandy', 21, 'test4@baomidou.com'),
    7. (5, 'Billie', 24, 'test5@baomidou.com');

    四、IDEA创建项目并导入相关依赖

    4.1、Spring Initializr快速创建springboot项目,版本2.2.1

    4.2、导入依赖

    ```xml
  1. <dependency>
  2. <groupId>com.baomidou</groupId>
  3. <artifactId>mybatis-plus-boot-starter</artifactId>
  4. <version>3.0.5</version>
  5. </dependency>
  6. <!--mysql-->
  7. <dependency>
  8. <groupId>mysql</groupId>
  9. <artifactId>mysql-connector-java</artifactId>
  10. </dependency>
  11. <!--lombok用来简化实体类-->
  12. <dependency>
  13. <groupId>org.projectlombok</groupId>
  14. <artifactId>lombok</artifactId>
  15. </dependency>
  1. **注意:**引入 `MyBatis-Plus` 之后请不要再次引入 `MyBatis` 以及 `MyBatis-Spring`,以避免因版本差异导致的问题。
  2. <a name="N21Qg"></a>
  3. ## 4.3、在IDEA中安装lombok插件(略)
  4. <a name="Ov7mZ"></a>
  5. ## 4.4、配置application.yml
  6. mysql8.0以下,不需要配置时区,8.0及以上版本需要设置时区
  7. ```properties
  8. #mysql数据库连接
  9. spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  10. spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus
  11. spring.datasource.username=root
  12. spring.datasource.password=123456

mysql8.0及以上

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

4.5、编写代码

(1)主启动类中加上**@MapperScan** 注解,扫描 Mapper 文件夹

  1. @SpringBootApplication
  2. @MapperScan("com.guang.mapper")//mapper接口没有实现类,所以需要加上
  3. public class MybatisPlusApplication {
  4. ......
  5. }

(2)实体类

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

lombok的使用参考:https://blog.csdn.net/motui/article/details/79012846

(3)mapper

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

(4)单元测试查看效果

  1. @Autowired
  2. private UserMapper userMapper;
  3. @Test
  4. void contextLoads() {
  5. List<User> users = userMapper.selectList(null);
  6. for (User u : users) {
  7. System.out.println(u);
  8. }
  9. }

(5)添加mybatis-plus日志,查看sql执行情况

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

五、插入操作-insert

  1. @Test
  2. void insertUser(){
  3. User user=new User();
  4. user.setAge(12);
  5. user.setName("Lisa");
  6. user.setEmail("Lisa@qq.com");
  7. int insert = userMapper.insert(user);
  8. System.out.println("insert影响的行数:"+insert);
  9. }

数据库没有设置id自增,mybatis-plus自动生成id策略long类型(19位),mp自带方式

六、分布式id生成策略

分布式id生成策略:参考https://www.cnblogs.com/haoxinyue/p/5208136.html

1、数据库自增长序列或字段(数据库设置自增)
分库分表时,得获取上一张表的最后一条数据的id
2、UUID
不利于进行排序

3、UUID的变种
4、Redis生成ID,原子操作
5、Twitter的snowflake(雪花)算法19位,mp自带的方式
6、利用zookeeper生成唯一ID
7、MongoDB的ObjectId

七、mp设置id策略

(1)ID_WORKER默认生成19位Long为id
MyBatis-Plus默认的主键策略是:IDWORKER 全局唯一ID_

(2)ID_WORKER_STR默认生成19为字符串类型id

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

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

(4)其他主键生成策略

  1. /**
  2. * 数据库ID自增
  3. */
  4. AUTO(0),
  5. /**
  6. * 该类型为未设置主键类型
  7. */
  8. NONE(1),
  9. /**
  10. * 用户输入ID
  11. * 该类型可以通过自己注册自动填充插件进行填充
  12. */
  13. INPUT(2),
  14. /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
  15. /**
  16. * 全局唯一ID (idWorker)
  17. */
  18. ID_WORKER(3),
  19. /**
  20. * 全局唯一ID (UUID)
  21. */
  22. UUID(4),
  23. /**
  24. * 字符串全局唯一ID (idWorker 的字符串表示)
  25. */
  26. ID_WORKER_STR(5);

设置全局id主键生成策略,所有的实体类主键根据该策略生成在配置文件中添加

  1. #全局设置主键生成策略
  2. mybatis-plus.global-config.db-config.id-type=auto

八、根据id更新操作-updateById

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

  1. @Test
  2. public void testUpdateById(){
  3. User user = new User();
  4. user.setId(1L);
  5. user.setAge(28);
  6. int result = userMapper.updateById(user);
  7. //结果影响行数
  8. System.out.println(result);
  9. }

九、自动填充功能实现

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:

9.1、手动填充

在创建或者更新的时候,自行将属性set进去。

9.2、mybatis-plus自动填充

(1)数据库表中添加自动填充字段
在User表中添加datetime类型的新的字段 create_time、update_time

(2)在实体类添加相应字段并在该属性上添加注解

  1. @TableField(fill = FieldFill.INSERT)
  2. private Date createTime;
  3. @TableField(fill = FieldFill.INSERT_UPDATE)//添加修改时都有值
  4. private Date updateTime;

(3)创建MyMetaObjectHandler处理器并实现 MetaObjectHandler接口

  1. @Component
  2. public class MyMetaObjectHandler implements MetaObjectHandler {
  3. //
  4. private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);
  5. //添加时更新创建时间、更新时间
  6. @Override
  7. public void insertFill(MetaObject metaObject) {
  8. LOGGER.info("start insert fill ....");
  9. this.setFieldValByName("createTime", new Date(), metaObject);
  10. this.setFieldValByName("updateTime", new Date(), metaObject);
  11. }
  12. //修改时更新修改时间
  13. @Override
  14. public void updateFill(MetaObject metaObject) {
  15. LOGGER.info("start update fill ....");
  16. this.setFieldValByName("updateTime", new Date(), metaObject);
  17. }
  18. }

十、乐观锁、悲观锁

10 乐观锁.png

10.1、乐观锁-丢失更新

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

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

(1)数据库中添加version字段

  1. ALTER TABLE `user` ADD COLUMN `version` INT

(2)在实体类添加version字段并添加@version注解、@TableField设置初始值

  1. @Version
  2. @TableField(fill = FieldFill.INSERT)
  3. private Integer version;

(3)元对象处理器接口添加version的insert默认值

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

特别说明:

  • 支持的数据类型只有 int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!

(4)创建配置类MybatisPlusConfig并注册乐观锁bean

  1. @EnableTransactionManagement//事务的支持
  2. @Configuration
  3. @MapperScan("com.guang.mapper")//去掉主启动类中的包扫描
  4. public class MybatisPlusConfig {
  5. /**
  6. * 乐观锁插件
  7. */
  8. @Bean
  9. public OptimisticLockerInterceptor optimisticLockerInterceptor() {
  10. return new OptimisticLockerInterceptor();
  11. }
  12. }

(5)测试乐观锁成功,version更新

  1. public void testOptimisticLocker() {
  2. //查询
  3. User user = userMapper.selectById(1L);
  4. //修改数据
  5. user.setName("Helen Yao");
  6. user.setEmail("helen@qq.com");
  7. //执行更新
  8. userMapper.updateById(user);
  9. }

(6)测试乐观锁失败案例

  1. public void testOptimisticLockerFail() {
  2. //查询
  3. User user = userMapper.selectById(1L);
  4. //修改数据
  5. user.setName("Helen Yao1");
  6. user.setEmail("helen@qq.com1");
  7. //模拟取出数据后,数据库中version实际数据比取出的值大,即已被其它线程修改并更新了version
  8. user.setVersion(user.getVersion() - 1);
  9. //执行更新
  10. userMapper.updateById(user);
  11. }

10.2、悲观锁

串行操作:当一人正在修改该条数据时,另一个人不可以对此条记录进行操作。

十一、多种普通操作

11.1、根据id进行单条查询

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

11.2、多个id批量查询list集合装id

  1. @Test
  2. public void testSelectBatchIds(){
  3. List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
  4. users.forEach(System.out::println);
  5. }

11.3、条件查询map的key装字段,value装值

  1. @Test
  2. public void testSelectByMap(){
  3. HashMap<String, Object> map = new HashMap<>();
  4. map.put("name", "Helen");
  5. map.put("age", 18);
  6. List<User> users = userMapper.selectByMap(map);
  7. }

11.4、分页查询

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

1、删除主类上的@MapperScan扫描注解
2、在配置类中添加分页插件

  1. @Configuration
  2. @MapperScan("com.guang.mapper")
  3. public class MybatisPlusConfig {
  4. /**
  5. * 分页插件
  6. */
  7. @Bean
  8. public PaginationInterceptor paginationInterceptor() {
  9. return new PaginationInterceptor();
  10. }
  11. }

(1)不带条件的分页查询测试,通过page对象获取相关数据

  1. @Test
  2. public void testSelectPage() {
  3. Page<User> page = new Page<>(1,5);
  4. userMapper.selectPage(page, null);//条件参数为null
  5. page.getRecords().forEach(System.out::println);//每页数据的list集合
  6. System.out.println(page.getCurrent());//当前页
  7. System.out.println(page.getPages());//总页数
  8. System.out.println(page.getSize());//每页显示记录数
  9. System.out.println(page.getTotal());//总记录数
  10. System.out.println(page.hasNext());//是否有下一页
  11. System.out.println(page.hasPrevious());//是否有上一页
  12. }

控制台sql语句输出:SELECT id,name,age,email,create_time,update_time FROM user LIMIT 0,5

(2)带条件的分页查询测试-QueryWrapper

  1. @Test
  2. public void testSelectPageCondition() {
  3. Page<EduTeacher> page = new Page<>(current, limit);
  4. if (null == page) return R.ERROE();
  5. QueryWrapper wrapper = new QueryWrapper();
  6. String name = teacherVo.getName();
  7. Integer level = teacherVo.getLevel();
  8. String begin = teacherVo.getBegin();
  9. String end = teacherVo.getEnd();
  10. if (null != name) {
  11. wrapper.like("name", name);
  12. }
  13. if (null != level) {
  14. wrapper.eq("level", level);
  15. }
  16. if (null != begin) {
  17. wrapper.ge("gmt_create", begin);
  18. }
  19. if (null != end) {
  20. wrapper.ge("gmt_create", end);
  21. }
  22. teacherService.page(page, wrapper);
  23. long summary = page.getTotal();
  24. List<EduTeacher> list = page.getRecords();
  25. HashMap<String, Object> resultmap = new HashMap<>();
  26. resultmap.put("total", summary);
  27. resultmap.put("item", list);
  28. return R.OK().setData(resultmap);
  29. }

十二、实现删除功能

12.1、物理删除

1、单个删除

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

2、批量删除list装id

  1. @Test
  2. public void testDeleteBatchIds() {
  3. int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
  4. System.out.println(result);
  5. }

3、条件删除map的key装字段value装值

  1. @Test
  2. public void testDeleteByMap() {
  3. HashMap<String, Object> map = new HashMap<>();
  4. map.put("name", "Helen");
  5. map.put("age", 18);
  6. int result = userMapper.deleteByMap(map);
  7. System.out.println(result);
  8. }

12.2、逻辑删除

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录,但是在查询时,mp会自动过滤掉此纪录,不会显示出来

(1)数据库中添加 deleted字段,可设置默认值为0表示未删除

  1. ALTER TABLE `user` ADD COLUMN `deleted` boolean

(2)实体类添加deleted 字段
并加上 @TableLogic 注解
如果数据库未设置默认值,得在代码中添加时设置默认值, @TableField(fill = FieldFill.INSERT) 注解

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

(3)数据库没有设置默认值时,元对象处理器接口添加deleted的insert默认值

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

(4)application.properties 加入配置

  1. #1表示已经被删除
  2. mybatis-plus.global-config.db-config.logic-delete-value=1
  3. #0表示未删除
  4. mybatis-plus.global-config.db-config.logic-not-delete-value=0

(5)在配置类MybatisPlusConfig中注册逻辑删除插件

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

(6)测试逻辑删除功能
1、删除数据库某条记录
2、该条记录的deleted字段值被修改为1
3、再次查询数据库记录时,该条记录将会被过滤
控制台sql语句:SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0

十三、性能分析插件

性能分析拦截器,用于输出每条 SQL 语句及其执行时间,便于优化
SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题

13.1、配置插件

(1)参数说明
参数:maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题。
参数:format: SQL是否格式化,默认false。
(2)在配置类 MybatisPlusConfig 中配置插件

  1. /**
  2. * SQL 执行性能分析插件
  3. * 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
  4. */
  5. @Bean
  6. @Profile({"dev","test"})// 设置 dev test 环境开启
  7. public PerformanceInterceptor performanceInterceptor() {
  8. PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
  9. performanceInterceptor.setMaxTime(500);//500ms,超过此处设置的ms则sql不执行
  10. performanceInterceptor.setFormat(true);
  11. return performanceInterceptor;
  12. }

(3)application.properties中设置dev环境

  1. #环境设置:devtestprod
  2. spring.profiles.active=dev

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

十四、mp实现复杂查询-QueryWrapper

14.1、ge、gt、le、lt

  1. //大于等于、大于、小于等于、小于
  2. QueryWrapper<User> wrapper=new QueryWrapper();
  3. //查询年龄大于等于30的User
  4. wrapper.ge("age",30);
  5. List<User> list=userMapper.selectList(wrapper);

14.2、eq、ne

  1. //等于、不等于
  2. QueryWrapper<User> wrapper=new QueryWrapper();
  3. //查询name等于李雷的User
  4. wrapper.eq("name","李雷");
  5. List<User> list=userMapper.selectList(wrapper);

14.3、between、notBetween

  1. //在*与*之间、不在*与*之间
  2. QueryWrapper<User> wrapper=new QueryWrapper();
  3. //查询age在20到30之间的User
  4. wrapper.between("age",2030);
  5. List<User> list=userMapper.selectList(wrapper);

14.4、like

  1. //模糊查询
  2. QueryWrapper<User> wrapper=new QueryWrapper();
  3. //查询name中有“岳”字的User
  4. wrapper.like("name","岳");
  5. List<User> list=userMapper.selectList(wrapper);

14.5、orderByDesc、orderByAsc

  1. //降序排列、升序排列
  2. QueryWrapper<User> wrapper=new QueryWrapper();
  3. //查询按照年龄降序的User集合
  4. wrapper.orderByDesc("age");
  5. List<User> list=userMapper.selectList(wrapper);

14.6、last

  1. //想查询语句中拼接sql语句
  2. wrapper.last("limit 1");

14.7、查询指定的列

  1. wrapper.select("id","name");

十五、mp实现其他查询

1、delete
2、selectOne
3、selectCount
4、selectList
5、selectMaps
6、selectObjs
7、update