文档 官方文档:https://baomidou.com/ 
简介 MyBatis-Plus (opens new window) (简称 MP)是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性 
无侵入 :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小 :启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作 
入门案例 创建数据库 1 2 3 4 5 6 7 8 9 10 CREATE  DATABASE `mybatis_plus` ; USE `mybatis_plus`; CREATE  TABLE  `user ` (	`id` INT  ( 20  ) NOT  NULL  COMMENT '主键ID' , 	`name` VARCHAR  ( 30  ) DEFAULT  NULL  COMMENT '姓名' , 	`age` INT  ( 11  ) DEFAULT  NULL  COMMENT '年龄' , 	`email` VARCHAR  ( 50  ) DEFAULT  NULL  COMMENT '邮箱' , PRIMARY  KEY ( `id` ) ) ENGINE =  INNODB DEFAULT  CHARSET =  utf8; 
添加数据 1 2 3 4 5 6 7 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'  ); 
新建一个工程 
引入依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency >     <groupId > org.projectlombok</groupId >      <artifactId > lombok</artifactId >  </dependency > <dependency >     <groupId > com.baomidou</groupId >      <artifactId > mybatis-plus-boot-starter</artifactId >      <version > 3.5.1</version >  </dependency > <dependency >     <groupId > mysql</groupId >      <artifactId > mysql-connector-java</artifactId >      <scope > runtime</scope >  </dependency > 
安装lombok插件 
第一个查询案例 首先新建一个实体类 pojo.User
1 2 3 4 5 6 7 @Data public  class  User      Integer id;     String name;     Integer age;     String email; } 
然后添加对应的mapper映射文件 mapper.UserMapper
1 2 3 4 5 6 7 8 public  interface  UserMapper  extends  BaseMapper <User > } 
在启动类上添加 @MapperScan 自动扫描 mapper 接口
1 2 3 4 5 6 7 8 9 @SpringBootApplication @MapperScan("com.szx.mybatisplusproduct.mapper") public  class  MybatisPlusProductApplication      public  static  void  main (String[] args)  throws  UnknownHostException         SpringApplication.run(MybatisPlusProductApplication.class, args);     } } 
添加测试文件
1 2 3 4 5 6 7 8 9 10 11 @SpringBootTest public  class  UserTest1      @Autowired      UserMapper userMapper;     @Test      public  void  test1 ()          List<User> userList = userMapper.selectList(null );         userList.forEach(item-> System.out.println("item = "  + item));     } } 
运行测试方法查看返回
添加日志打印 在 application.yml 配置文件中添加如下配置即可
1 2 3 mybatis-plus:   configuration:      log-impl:  org.apache.ibatis.logging.stdout.StdOutImpl  
添加完成后会在控制台输入查询的sql语句信息
BaseMapper BaseMapper提供了很多基础方法,我们可以使用提供的方法来实现一个表基本的增删改查
新增方法 1 2 3 4 5 6 7 8 9 10 11 @Test public  void  testInsert ()     User user = new  User();     user.setName("张三" );     user.setAge(18 );     user.setEmail("zhangsan@123.com" );          int  insert = userMapper.insert(user);           System.out.println(user.getId()); } 
查看插入表中的结果
删除方法 根据id删除 1 2 3 4 5 6 7 @Test public  void  testDeleteById ()          int  resDelCount = userMapper.deleteById(1 );     System.out.println("resDelCount = "  + resDelCount); } 
根据条件删除 1 2 3 4 5 6 7 8 9 10 @Test public  void  testDeleteByMap ()          HashMap<String, Object> map = new  HashMap<>();     map.put("name" ,"张三" );     map.put("age" ,18 );     int  i = userMapper.deleteByMap(map);     System.out.println("i = "  + i); } 
根据id集合批量删除数据 1 2 3 4 5 6 7 @Test public  void  testBatchDelete ()     List<Integer> ids = Arrays.asList(2 , 3 );     int  i = userMapper.deleteBatchIds(ids);     System.out.println("i = "  + i); } 
修改方法 1 2 3 4 5 6 7 8 9 @Test public  void  testUpdate ()     User user = new  User();     user.setId(2 );     user.setName("张三" );          int  i = userMapper.updateById(user);     System.out.println("i = "  + i); } 
查询方法 根据id查询单条数据 1 2 3 User user = userMapper.selectById(2 ); System.out.println("user = "  + user); 
根据id批量查询多条数据 1 2 3 4 List<Integer> ids = Arrays.asList(2 , 3 , 4 ); List<User> users = userMapper.selectBatchIds(ids); System.out.println("users = "  + users); 
根据map查询 1 2 3 4 5 6 HashMap<String, Object> searchMap = new  HashMap<>(); searchMap.put("name" ,"Jack" ); searchMap.put("age" ,20 ); List<User> users1 = userMapper.selectByMap(searchMap); System.out.println("users1 = "  + users1); 
根据条件查询 这里要传入一个条件构造器,没有时可以传入一个null
1 2 3 List<User> userList = userMapper.selectList(null ); userList.forEach(System.out::println); 
查询单个值 这里也是传入条件构造器,没有则传入null
例如:查询数据的总数
1 2 3 Long total = userMapper.selectCount(null ); System.out.println("total = "  + total); 
自定义SQL方法 首先添加自定义的mapper映射文件,在MyBatisPlus中已经帮我们定义了mapper映射文件的位置,就是在配置文件目录中添加 mapper 文件夹,然后自动会读取以任何目录下,任何名字的 xml 结尾的映射文件
所以首先在 mapper 目录中新建一个映射文件,映射文件的名字和接口名字一样
然后在接口中添加自定义查询方法
1 2 3 4 5 @Repository public  interface  UserMapper  extends  BaseMapper <User >          Map<String,Object> mySelectMapById (Integer id)  ; } 
添加映射sql
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper          PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"          "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper  namespace ="com.szx.mybatisplusproduct.mapper.UserMapper" >          <select  id ="mySelectMapById"  resultType ="map" >          select * from user where id = #{id}     </select >  </mapper > 
测试自定义的方法
1 2 3 4 5 @Test public  void  testSelectMap ()     Map<String, Object> userMap = userMapper.mySelectMapById(2 );     System.out.println("userMap = "  + userMap); } 
测试查看运行结果
IService 和 ServiceImpl 
IService 是 mybatis-plus 提供的一个封装了常用CRUD的一个接口,ServiceImpl 是它的实现类 
进一步封装CRUD采用如下的方式为前缀来区分各个方法,避免和 mapper 中的方法混淆
get 单行查询 
remove 删除 
list 查询集合 
page 分页 
 
 
 
创建Service接口和实现类 新建 UserService 接口,继承 IService
1 2 3 4 5 6 7 8 public  interface  UserService  extends  IService <User >      } 
然后添加实现类,继承 ServiceImpl 类
1 2 3 4 5 6 7 8 9 @Service public  class  UserServiceImpl  extends  ServiceImpl <UserMapper , User > implements  UserService } 
查询数据总条数 1 2 3 4 5 6 7 8 9 10 11 12 @Autowired UserServiceImpl userService; @Test public  void  testGetCount ()     long  count = userService.count();     System.out.println("count = "  + count); } 
批量插入 由于sql语句有长度限制,海量的数据插入无法放在单条的sql中实现,所以在Service接口中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Autowired UserServiceImpl userService; @Test public  void  testBatchSave ()     ArrayList<User> users = new  ArrayList<>();     for  (int  i = 0 ; i < 5 ; i++) {         User user = new  User();         user.setName("abc"  + i);         user.setAge(20  + i);         users.add(user);     }          boolean  b = userService.saveBatch(users);     System.out.println("b = "  + b); } 
批量删除 1 2 3 4 5 6 7 8 9 10 11 @Autowired UserServiceImpl userService; @Test public  void  testBatchRemove ()     List<Integer> ids = Arrays.asList(6 , 7 , 8 , 9 );     boolean  b = userService.removeBatchByIds(ids);     System.out.println("b = "  + b); } 
常用注解 @TableName 作用:指定实体类对应的表名
在上面的例子中,我们没有指定数据库的表名,只是在继承 BaseMapper 时指定了一个泛型 User,从而mybatis-plus就知道我们操作的是 user 表,如果我们数据库中的表名和实例类类名不一致,会怎么样呢?
我们修改数据库的表名为 t_user
然后执行查询方法
1 2 3 4 5 6 7 8 9 10 11 12 @Autowired UserServiceImpl userService; @Test public  void  testGetCount ()     long  count = userService.count();     System.out.println("count = "  + count); } 
会出现如下错误
这时候我们可以在实体类上用 @TableName 来指明要操作的表名
1 2 3 4 5 6 7 8 @TableName("t_user") @Data public  class  User      Integer id;     String name;     Integer age;     String email; } 
此时再来查询就可以正常查询了
全局配置表名前缀 1 2 3 4 5 6 7 mybatis-plus:   configuration:      log-impl:  org.apache.ibatis.logging.stdout.StdOutImpl    global-config:      db-config:               table-prefix:  t_  
@TableId 作用:指定字段为主键id
在上面的插入数据测试中,mybatis-plus会默认吧id当做主键,并根据雪花算法自动生成id,如果数据库中的主键不叫 id,则是否出出现问题呢?
现在修改表中的 id为 t_id
修改实体类中的 id 也为 t_id
1 2 3 4 5 6 7 8 @TableName("t_user") @Data public  class  User      Integer t_id;     String name;     Integer age;     String email; } 
此时来执行一个新增操作
1 2 3 4 5 6 7 8 9 @Test public  void  testSave ()     User user = new  User();     user.setName("Jack" );     user.setAge(18 );     user.setEmail("jack@123.com" );     boolean  save = userService.save(user);     System.out.println("save = "  + save); } 
出现如下错误
这时我们使用 @TableId 来指定一个属性为主键
1 2 3 4 5 6 7 8 9 10 @TableName("t_user") @Data public  class  User      Integer id;     @TableId      Integer t_id;     String name;     Integer age;     String email; } 
再次执行新建就会新建成功了
@TableId的value属性 若表中的主键名为 t_id,实体类中对应的还是 id,则会抛出如下异常,表示 mybatis-plus 依然吧 id 当做主键
这时就用 @TableId("t_id") 来指定主键的名称
1 2 3 4 5 6 7 8 9 @TableName("t_user") @Data public  class  User      @TableId("t_id")      Integer id;     String name;     Integer age;     String email; } 
再次测试即可添加成功
@TableId的type属性 type属性主要用来定义主键策略
常用的策略
值 
描述 
 
 
IdType.ASSIGN_ID(默认) 
基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 
 
IdType.AUTO 
使用数据库的自增策略,注意,该类型请确保数据库设置了id自增, 
 
测试使用数据库的主键递增策略
1 2 3 4 5 6 7 8 9 @TableName("t_user") @Data public  class  User      @TableId(value = "t_id",type = IdType.AUTO)      Integer id;     String name;     Integer age;     String email; } 
现在数据库的最大id为6
执行一次新增方法
再次查看数据库记录,最新的记录id为7
@TableField 定义实体类中的属性对应的是表中的那个列。例如,我表中的有一个列为 user_name,而实体类中对应的却是 name,那么这种情况下新建会不会有问题呢?
现在吧数据库表中的列名改一下
实体类还是这样
1 2 3 4 5 6 7 8 9 @TableName("t_user") @Data public  class  User      @TableId(value = "t_id",type = IdType.AUTO)      Integer id;     String name;     Integer age;     String email; } 
运行新增方法,出现如下错误,找不到 name 列
这时,可以用 @TableField 注解来声明这个属性对应哪一个列
1 2 3 4 5 6 7 8 9 10 @TableName("t_user") @Data public  class  User      @TableId(value = "t_id",type = IdType.AUTO)      Integer id;     @TableField("user_name")      String name;     Integer age;     String email; } 
此时再来新建就可以了
驼峰命名规则 如果数据库中使用的是下划线的方式来命名字段,例如 user_name,实体类中使用的是 userName,则新建不会出错,这时因为 mybatis 会按照驼峰命名去匹配字段名称
@TableLogic 表示逻辑删除数据
物理删除:真正的从数据表中删除这条数据,无法恢复 
逻辑删除:不是真正的从表中删除这个数据,只是根据一个状态来表示这个数据被删除,查询时不会查询状态是删除的数据 
 
实现在表中新增一个字段,用来表示是否删除,注意要设置一个默认值为0,0表示未删除,1表示已删除
然后再实体类中添加对应的属性,并添加 @TableLogic 注解
1 2 3 4 5 6 7 8 9 10 11 12 13 @TableName("t_user") @Data public  class  User      @TableId(value = "t_id",type = IdType.AUTO)      Integer id;     @TableField("user_name")      String name;     Integer age;     String email;          @TableLogic      Integer isDeleted; } 
然后执行删除操作
1 2 3 4 5 @Test public  void  testRemoveById ()     boolean  b = userService.removeById(8 );     System.out.println("b = "  + b); } 
执行结果
通过执行结果可以看到,实际运行的SQL时修改操作,此时来看表中id为8的数据是否还在
通过结果可以看出id等于8的数据并没有被删除,只是吧状态改成了1
然后再来一下查询方法,是否会把已删除的数据查到
1 2 3 4 5 @Test public  void  testGetUserList ()     List<User> userList = userService.list();     userList.forEach(System.out::println); } 
运行结果
会自动添加一个 is_deleted = 0 的查询条件
条件构造器和常用接口 wapper介绍 
QueryWrapper 组装查询条件 例如:
查询姓名包含 a,并且年龄在 20 到 30 岁之间,并且邮箱不为空的数据
1 2 3 4 5 6 7 8 9 10 @Test public  void  test1 ()     QueryWrapper<User> qw = new  QueryWrapper<>();          qw.like("user_name" ,"a" )             .between("age" ,20 ,30 )             .isNotNull("email" );     List<User> userList = userService.list(qw);     userList.forEach(System.out::println); } 
查询结果中的SQL语句
1 SELECT  t_id AS  id,user_name AS  name,age,email,is_deleted FROM  t_user WHERE  is_deleted= 0  AND  (user_name LIKE  ? AND  age BETWEEN  ? AND  ? AND  email IS  NOT  NULL )
成功的查询到两条数据
组装排序条件 按照用户年龄降序排序,如果年龄相同,则按照id升序排序
1 2 3 4 5 6 7 8 @Test public  void  test2 ()     QueryWrapper<User> qw = new  QueryWrapper<>();          qw.orderByDesc("age" ).orderByAsc("t_id" );     List<User> userList = userService.list(qw);     userList.forEach(System.out::println); } 
查询结果中的SQL语句
1 SELECT  t_id AS  id,user_name AS  name,age,email,is_deleted FROM  t_user WHERE  is_deleted= 0  ORDER  BY  age DESC ,t_id ASC 
返回结果
组装删除条件 删除邮箱为空的数据
1 2 3 4 5 6 7 @Test public  void  test3 ()     QueryWrapper<User> qw = new  QueryWrapper<>();     qw.isNull("email" );     boolean  remove = userService.remove(qw);     System.out.println("remove = "  + remove); } 
查询结果中的SQL语句
1 UPDATE t_user SET  is_deleted= 1  WHERE  is_deleted= 0  AND  (email IS  NULL ) 
返回结果
条件的优先级 将年龄大于20并且姓名中包含有a的或者邮箱为空的数据修改
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public  void  test4 ()          QueryWrapper<User> qw = new  QueryWrapper<>();     qw.gt("age" ,20 )             .like("user_name" ,"a" )             .or()             .isNull("email" );     User user = new  User();     user.setAge(18 );     boolean  update = userService.update(user, qw);     System.out.println("update = "  + update); } 
运行的SQL
1 UPDATE t_user SET  age= ? WHERE  is_deleted= 0  AND  (age >  ? AND  user_name LIKE  ? OR  email IS  NULL ) 
使用 lambda 表达式
1 2 3 4 5 6 7 8 9 10 @Test public  void  test5 ()     QueryWrapper<User> qw = new  QueryWrapper<>();     qw.like("user_name" ,"a" )             .and(i->i.gt("age" ,20 ).or().isNull("email" ));     User user = new  User();     user.setAge(18 );     boolean  update = userService.update(user, qw);     System.out.println("update = "  + update); } 
运行的SQL,lambda 表达式内的逻辑优先运算
1 UPDATE t_user SET  age= ? WHERE  is_deleted= 0  AND  (user_name LIKE  ? AND  (age >  ? OR  email IS  NULL )) 
组装select子句 比如我只查询 user_name 和 age
1 2 3 4 5 6 7 8 @Test public  void  test6 ()     QueryWrapper<User> qw = new  QueryWrapper<>();     qw.select("user_name" ,"age" );          Map<String, Object> map = userService.getMap(qw);     System.out.println("map = "  + map); } 
SQL语句
1 SELECT  user_name,age FROM  t_user WHERE  is_deleted= 0 
实现子查询 查询年龄小于等于20的数据
1 2 3 4 5 6 7 8 @Test public  void  test7 ()     String sql = "select age from t_user where age <= 20" ;     QueryWrapper<User> qw = new  QueryWrapper<>();     qw.inSql("age" ,sql);     List<User> list = userService.list(qw);     list.forEach(System.out::println); } 
生成的SQL
1 SELECT  t_id AS  id,user_name AS  name,age,email,is_deleted FROM  t_user WHERE  is_deleted= 0  AND  (age IN  (select  age from  t_user where  age <=  20 ))
查询结果
UpdateWrapper 1 2 3 4 5 6 7 8 9 10 11 @Test public  void  test1 ()     UpdateWrapper<User> up = new  UpdateWrapper<>();     up.like("user_name" ,"a" )             .and(i->i.gt("age" ,20 ).or().isNull("email" ))             .set("user_name" ,"张三" )             .set("email" ,"zhangsan@123.com" );          boolean  update = userService.update(null , up);     System.out.println("update = "  + update); } 
生成的SQL
1 UPDATE t_user SET  user_name= ?,email= ? WHERE  is_deleted= 0  AND  (user_name LIKE  ? AND  (age >  ? OR  email IS  NULL )) 
运行结果
模拟开发场景中的条件查询 思路一:复杂版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Test public  void  test2 ()     String user_name = "" ;     Integer ageStart = 15 ;     Integer ageEnd = 30 ;     QueryWrapper<User> qw = new  QueryWrapper<>();          if (StringUtils.isNotBlank(user_name)){         qw.like("user_name" ,user_name);     }     if (ageStart != null ){                  qw.ge("age" ,ageStart);     }     if (ageEnd != null ){                  qw.le("age" ,ageEnd);     }     List<User> list = userService.list(qw);     list.forEach(System.out::println); } 
生成的sql语句
1 SELECT  t_id AS  id,user_name AS  name,age,email,is_deleted FROM  t_user WHERE  is_deleted= 0  AND  (age >=  ? AND  age <=  ?)
返回的结果
这种方法过于繁琐,下面我们有更简单的方式来查询
思路二:condition 我们可以使用带condition参数的重载方法构建查询条件,简化代码的编写
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public  void  test3 ()          String user_name = "" ;     Integer ageStart = 15 ;     Integer ageEnd = 30 ;     QueryWrapper<User> qw = new  QueryWrapper<>();     qw.like(StringUtils.isNotBlank(user_name),"user_name" ,user_name)             .ge(ageStart != null ,"age" ,ageStart)             .le(ageEnd != null ,"age" ,ageEnd);     List<User> list = userService.list(qw);     list.forEach(System.out::println); } 
LambdaQueryWrapper 使用 lambda 表达式来表示操作的列名,避免写时字符串导致出错
1 2 3 4 5 6 7 8 9 10 11 12 @Test public  void  test ()     String user_name = "a" ;     Integer ageStart = 15 ;     Integer ageEnd = 30 ;     LambdaQueryWrapper<User> qw = new  LambdaQueryWrapper<>();     qw.like(StringUtils.isNotBlank(user_name),User::getName,user_name)             .ge(ageStart != null ,User::getAge,ageStart)             .le(ageEnd != null ,User::getAge,ageEnd);     List<User> list = userService.list(qw);     list.forEach(System.out::println); } 
对应的sql
1 SELECT  t_id AS  id,user_name AS  name,age,email,is_deleted FROM  t_user WHERE  is_deleted= 0  AND  (user_name LIKE  ? AND  age >=  ? AND  age <=  ?)
查询结果
LambdaUpdateWrapper 1 2 3 4 5 6 7 8 9 10 11 @Test public  void  test ()               LambdaUpdateWrapper<User> up = new  LambdaUpdateWrapper<>();     up.set(User::getAge,22 )             .set(User::getName,"updateWrapper" )             .like(User::getName,"a" );     boolean  update = userService.update(up);     System.out.println("update = "  + update); } 
执行的SQL
1 UPDATE t_user SET  age= ?,user_name= ? WHERE  is_deleted= 0  AND  (user_name LIKE  ?) 
插件 分页插件 mybatis-plus 自带分页插件,通过简单的配置即可
首先新建配置类
1 2 3 4 5 6 7 8 9 @Configuration public  class  MybatisPlusConfig      @Bean      public  MybatisPlusInterceptor mybatisPlusInterceptor ()          MybatisPlusInterceptor interceptor = new  MybatisPlusInterceptor();         interceptor.addInnerInterceptor(new  PaginationInnerInterceptor(DbType.MYSQL));         return  interceptor;     } } 
测试分页加条件查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public  void  test ()          Page<User> userPage = new  Page<>(1 , 5 );          LambdaQueryWrapper<User> qw = new  LambdaQueryWrapper<>();     qw.like(User::getName,"a" );          Page<User> page = userService.page(userPage,qw);               List<User> userList = page.getRecords();     userList.forEach(System.out::println);     System.out.println("********分页信息********" );     System.out.println("表中总数:"  + page.getTotal());     System.out.println("是否有上一页:"  + page.hasPrevious());     System.out.println("是否有下一页:"  + page.hasNext());     System.out.println("当前第几页:"  + page.getCurrent());     System.out.println("一共有几页:"  + page.getPages());     System.out.println("每页显示的条数:"  + page.getSize()); } 
生成的SQL
1 SELECT  t_id AS  id,user_name AS  name,age,email,is_deleted FROM  t_user WHERE  is_deleted= 0  AND  (user_name LIKE  ?) LIMIT ?
查询结果
自定义分页查询方法 在 UserMapper 接口中新增接口
1 2 3 4 5 @Repository public  interface  UserMapper  extends  BaseMapper <User > 	     Page<User> selectMyPageVo (Page<User> page,Integer age)  ; } 
在 UserMapper.xml 中添加接口映射
1 2 3 4 5 6 <sql  id ="baseColunm" > t_id,user_name,email,age</sql > <select  id ="selectMyPageVo"  resultType ="com.szx.mybatisplusproduct.pojo.User" >     select <include  refid ="baseColunm" > </include >  from t_user where age >= #{age} </select > 
测试
1 2 3 4 5 6 7 8 9 10 @Autowired UserMapper userMapper; @Test public  void  test2 ()     Page<User> page = new  Page<>(1 , 5 );     Page<User> userPage = userMapper.selectMyPageVo(page, 20 );     List<User> records = userPage.getRecords();     records.forEach(System.out::println); } 
运行结果
图中的 id 和 name 都是null,这是因为 user 实体类中的属性和表中的字段不一致导致
乐观锁 现有如下场景:一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
模拟上面的冲突 首先新建一个商品表并插入一条数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 SET  NAMES utf8mb4;SET  FOREIGN_KEY_CHECKS =  0 ;DROP  TABLE  IF EXISTS  `t_product`;CREATE  TABLE  `t_product`  (  `id` int (0 ) NOT  NULL  AUTO_INCREMENT COMMENT '主键ID' ,   `name` varchar (30 ) CHARACTER  SET  utf8mb4 COLLATE  utf8mb4_0900_ai_ci NULL  DEFAULT  NULL  COMMENT '商品名称' ,   `price` int (0 ) NULL  DEFAULT  0  COMMENT '价格' ,   `version` int (0 ) NULL  DEFAULT  0  COMMENT '乐观锁版本号' ,   PRIMARY  KEY (`id`) USING  BTREE ) ENGINE =  InnoDB CHARACTER  SET  =  utf8mb4 COLLATE  =  utf8mb4_0900_ai_ci ROW_FORMAT =  Dynamic ; INSERT  INTO  `t_product` VALUES  (1 , '外星人笔记本' , 100 , 0 );SET  FOREIGN_KEY_CHECKS =  1 ;
新建对应实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  com.szx.mybatisplusproduct.pojo;import  com.baomidou.mybatisplus.annotation.IdType;import  com.baomidou.mybatisplus.annotation.TableId;import  lombok.Data;@Data public  class  Product      @TableId(type = IdType.AUTO)      Integer id;     String name;     Integer price;     Integer version; } 
添加 Mapper
1 2 public  interface  ProductMapper  extends  BaseMapper <Product > } 
模拟上面的场景
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 public  class  ProductTest      @Autowired      ProductMapper productMapper;     @Test      public  void  test ()                   Product p1 = productMapper.selectById(1 );                  Product p2 = productMapper.selectById(1 );                  p1.setPrice(p1.getPrice() + 50 );         productMapper.updateById(p1);                  p2.setPrice(p2.getPrice() - 30 );         productMapper.updateById(p2);                  Product p3 = productMapper.selectById(1 );         System.out.println(p3);     } } 
最后获取的结果商品价格变成了 70 元
乐观锁的实现流程 数据库中添加version字段
取出记录时,获取当前version
1 SELECT  id,`name`,price,`version` FROM  product WHERE  id= 1 
更新时,version + 1,如果where语句中的version版本不对,则更新失败
1 UPDATE product SET  price= price+ 50 , `version`= `version` +  1  WHERE  id= 1  AND  `version`= 1  
MybatisPlus实现乐观锁 修改实体类,在版本号属性上添加 @Version
1 2 3 4 5 6 7 8 9 10 @Data public  class  Product      @TableId(type = IdType.AUTO)      Integer id;     String name;     Integer price;          @Version      Integer version; } 
在配置类中添加乐观锁插件
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public  class  MybatisPlusConfig      @Bean      public  MybatisPlusInterceptor mybatisPlusInterceptor ()          MybatisPlusInterceptor interceptor = new  MybatisPlusInterceptor();                  interceptor.addInnerInterceptor(new  PaginationInnerInterceptor(DbType.MYSQL));                  interceptor.addInnerInterceptor(new  OptimisticLockerInnerInterceptor());         return  interceptor;     } } 
再次测试价格修改流程
首先小李查询价格
1 SELECT  id,name,price,version FROM  t_product WHERE  id= ?
查到价格为100
然后小王查询价格
1 SELECT  id,name,price,version FROM  t_product WHERE  id= ?
查到价格也是100
然后小李修改价格
1 UPDATE t_product SET  name= ?, price= ?, version= ? WHERE  id= ? AND  version= ? 
并成功修改,同时版本号从1改成2
然后小王再去改版本号是1的价格
1 UPDATE t_product SET  name= ?, price= ?, version= ? WHERE  id= ? AND  version= ? 
由于上面小李已经把版本号改成2了,这时候小王再去修改版本号是1的数据,自然没有修改成功
最后老板再去查看价格
1 SELECT  id,name,price,version FROM  t_product WHERE  id= ?
价格为150
优化价格修改流程 添加一个修改失败再次请求的机制
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 @SpringBootTest public  class  ProductTest      @Autowired      ProductMapper productMapper;     @Test      public  void  test ()                   Product p1 = productMapper.selectById(1 );                  Product p2 = productMapper.selectById(1 );                  p1.setPrice(p1.getPrice() + 50 );         productMapper.updateById(p1);                  p2.setPrice(p2.getPrice() - 30 );         int  i = productMapper.updateById(p2);         if (i == 0 ){                          p2 = productMapper.selectById(1 );             p2.setPrice(p2.getPrice() - 30 );             productMapper.updateById(p2);         }                  Product p3 = productMapper.selectById(1 );         System.out.println(p3);     } } 
查询返回结果为120元
注意:每次测试前都重新吧数据库中的价格还原成100 
通用枚举 数据库中有些值的字段是固定的,例如性别,我们希望在修改这类值的时候可以从枚举类中来获取值并存入数据库
首先在 t_user 表中增加 sex 列
对应的实体类中添加映射sex属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @TableName("t_user") @Data public  class  User      @TableId(value = "t_id",type = IdType.AUTO)      Integer id;     @TableField("user_name")      String name;     Integer age;     String email;          @TableLogic      Integer isDeleted;     Integer sex; } 
添加枚举类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Getter public  enum  SexEnum      MALE(1 ,"男" ),     FEMALE(2 ,"女" );     @EnumValue       Integer sex;     String sexName;     SexEnum(Integer sex,String sexName){         this .sex = sex;         this .sexName = sexName;     } } 
配置扫描通用枚举
1 2 3 4 5 6 7 8 9 mybatis-plus:   configuration:      log-impl:  org.apache.ibatis.logging.stdout.StdOutImpl    global-config:      db-config:               table-prefix:  t_       type-enums-package:  com.szx.mybatisplusproduct.enums  
测试修改方法
1 2 3 4 5 6 7 8 9 10 11 12 @Autowired UserServiceImpl userService; @Test public  void  test ()          LambdaUpdateWrapper<User> up = new  LambdaUpdateWrapper<>();     up.set(User::getSex, SexEnum.FEMALE)             .like(User::getName,"a" );     boolean  update = userService.update(up);     System.out.println(update); } 
生成的sql
1 UPDATE t_user SET  sex= ? WHERE  is_deleted= 0  AND  (user_name LIKE  ?) 
运行结果
多数据源 适用于多个表的情况。我们创建两个库,分别为:mybatis_plus(以前的库不动)与mybatis_plus_1(新建),将mybatis_plus库的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功
首先新建一个数据
引入依赖
1 2 3 4 5 <dependency >      <groupId > com.baomidou</groupId >       <artifactId > dynamic-datasource-spring-boot-starter</artifactId >       <version > 3.5.0</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 server:   port:  8899  spring:   application:           name:  MyBatisPlus学习项目    datasource:      dynamic:               primary:  master               strict:  false        datasource:          master:            driver-class-name:  com.mysql.cj.jdbc.Driver            url:  jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8            username:  root            password:  abc123          slave_1:            driver-class-name:  com.mysql.cj.jdbc.Driver            url:  jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8            username:  root            password:  abc123  
创建 UserService
1 2 3 4 5 @DS("master")  @Service public  class  UserServiceImpl  extends  ServiceImpl <UserMapper , User > implements  UserService } 
创建 ProductService
1 2 3 4 5 @DS("slave_1") @Service public  class  ProductServiceImp  extends  ServiceImpl <ProductMapper , Product > implements  ProductService       } 
测试查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @SpringBootTest public  class  TestStarter      @Autowired      UserServiceImpl userService;     @Autowired      ProductServiceImp productService;     @Test      public  void  test ()                   List<User> userList = userService.list();         userList.forEach(System.out::println);         System.out.println("********" );                  List<Product> productList = productService.list();         productList.forEach(System.out::println);     } } 
返回的结果
可以看出两个数据库中的数据都被成功的查询到
代码生成器 首先安装插件
1 2 3 4 5 6 7 8 9 10 <dependency >      <groupId > com.baomidou</groupId >       <artifactId > mybatis-plus-generator</artifactId >       <version > 3.5.1</version >   </dependency >  <dependency >      <groupId > org.freemarker</groupId >           <artifactId > freemarker</artifactId >       <version > 2.3.31</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 package  com.szx.mybatisplusproduct;import  com.baomidou.mybatisplus.generator.FastAutoGenerator;import  com.baomidou.mybatisplus.generator.config.OutputFile;import  com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;import  java.util.Collections;public  class  FastAutoGeneratorTest      public  static  void  main (String[] args)           FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus? characterEncoding=utf-8&userSSL=false" , "root" , "abc123" )                 .globalConfig(builder -> {                     builder.author("szx" )                              .fileOverride()                              .outputDir("D://0000学习文件//MyBatisPlus" );                  })                 .packageConfig(builder -> {                     builder.parent("com.baomidou.mybatisplus.samples.generator" )                              .moduleName("system" )                              .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://0000学习文件//MyBatisPlus" ));                  })                 .strategyConfig(builder -> {                     builder.addInclude("t_user" )                              .addTablePrefix("t_" , "c_" );                  })                 .templateEngine(new  FreemarkerTemplateEngine())                  .execute();     } } 
运行结果
MyBatisX插件 MyBatis-Plus为我们提供了强大的mapper和service模板,能够大大的提高开发效率但是在真正开发过程中,MyBatis-Plus并不能为我们解决所有问题,例如一些复杂的SQL,多表联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可以使用MyBatisX插件MyBatisX一款基于 IDEA 的快速开发插件,为效率而生。
官方用法:https://baomidou.com/pages/ba5b24/ 
插件安装 搜索 mybatisx 并安装
配置数据源 
快速生成实体类,Mapper 
自动生成的文件
快速生成查询