MyBatisPlus

MyBatisPlus

今日目标

基于MyBatisPlus完成标准Dao的增删改查功能

掌握MyBatisPlus中的分页及条件查询构建

掌握主键ID的生成策略

了解MyBatisPlus的代码生成器

1. MyBatisPlus入门案例与简介

这一节我们来学习下MyBatisPlus的入门案例与简介。MyBatisPlus主要是对MyBatis的简化,先体会下它简化在哪,然后再学习它是什么,以及它帮我们都做哪些事。

1.1 入门案例

  • MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。

  • 开发方式

    • 基于MyBatis使用MyBatisPlus
      • 基于Spring使用MyBatisPlus
        • 基于SpringBoot使用MyBatisPlus

          SpringBoot刚刚我们学习完成,它能快速构建Spring开发环境用以整合其他技术,使用起来是非常简单,对于MP的学习,我们也基于SpringBoot来构建学习。

          学习之前,我们先来回顾下,SpringBoot整合Mybatis的开发过程:

          • 创建SpringBoot工程

          • 勾选配置使用的技术,能够实现自动添加起步依赖包

          • 设置dataSource相关属性(JDBC参数)

          • 定义数据层接口映射配置

            下面介绍一下SpringBoot整合Mybatisplus的流程:

            步骤1:创建SpringBoot工程

            步骤2:勾选配置使用技术

            说明:

            • 由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加

              步骤3:pom.xml补全依赖

               com.baomidou mybatis-plus-boot-starter 3.4.1 com.alibaba druid 1.1.16

              说明:

              • druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源

              • 从MP的依赖关系可以看出,通过依赖传递已经将MyBatis与MyBatis整合Spring的jar包导入,我们不需要额外在添加MyBatis的相关jar包

                步骤4:添加MP的相关配置信息

                resources默认生成的是properties配置文件,可以将其替换成yml文件,并在文件中配置数据库连接的相关信息:application.yml

                spring:
                  datasource:
                    type: com.alibaba.druid.pool.DruidDataSource
                    driver-class-name: com.mysql.cj.jdbc.Driver
                    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC 
                    username: root
                    password: root
                

                **说明:**serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为Asia/Shanghai

                步骤5:编写pojo类、创建dao接口\

                dao接口继承BaseMapper接口

                @Mapper
                public interface UserDao extends BaseMapper{}
                

                步骤6:编写引导类

                @SpringBootApplication
                //@MapperScan("com.itheima.dao")
                public class Mybatisplus01QuickstartApplication { public static void main(String[] args) { SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);
                    }
                }
                

                **说明:**Dao接口要想被容器扫描到,有两种解决方案:

                • 方案一:在Dao接口上添加@Mapper注解,并且确保Dao处在引导类所在包或其子包中

                  • 该方案的缺点是需要在每一Dao接口中添加注解
                  • 方案二:在引导类上添加@MapperScan注解,其属性为所要扫描的Dao所在包

                    • 该方案的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到,@Mapper就可以不写。

                      ==比较:==跟之前整合MyBatis相比,发现我们不需要在DAO接口中编写方法和SQL语句了,只需要继承BaseMapper接口即可。整体来说简化很多。

                      1.2 MybatisPlus简介

                      ==MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,==它是在MyBatis的基础上进行开发的,我们虽然使用MP但是底层依然是MyBatis的东西,也就是说我们也可以在MP中写MyBatis的内容,其旨在简化开发、提高效率

                      此处介绍一下MP的特性:

                      • 无侵入:只做增强不做改变,不会对现有工程产生影响

                      • 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作

                      • 支持 Lambda:编写查询条件无需担心字段写错

                      • 支持主键自动生成

                      • 内置分页插件

                      • ……

                        2. 标准数据层开发

                        这一节中重点学习的是数据层标准的CRUD(增删改查)的实现与分页功能。

                        2.1 标准CRUD使用

                        对于标准的CRUD功能都有哪些以及MP都提供了哪些方法可以使用呢?

                        这里对MP接口部分方法的参数进行简单说明:

                        • Serializable 序列化类
                              • 从这张图可以看出,
                                  • String和Number是Serializable的子类,
                                      • Number又是Float,Double,Integer等类的父类,
                                          • 能作为主键的数据类型都已经是Serializable的子类,
                                              • MP使用Serializable作为参数类型,就好比我们可以用Object接收任何数据类型一样。
                                                • Wrapper:用来构建条件查询的条件(where…),没有条件可直接传为Null

                                                  2.2 Lombok依赖

                                                  我们会发现DAO接口类的编写现在变成最简单的了,里面什么都不用写,只需要走继承BaseMapper接口即可。反过来看看模型类的编写都需要哪些内容:

                                                  • 私有属性

                                                  • setter…getter…方法

                                                  • toString方法

                                                  • 构造函数

                                                    概念

                                                    • Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。

                                                      Lombok依赖

                                                       org.projectlombok lombok 

                                                      **注意:**版本可以不用写,因为SpringBoot中已经管理了lombok的版本。

                                                      Lombok常见的注解有:

                                                      • @Setter:为模型类的属性提供setter方法
                                                        • @Getter:为模型类的属性提供getter方法

                                                        • @ToString:为模型类的属性提供toString方法

                                                        • @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法

                                                        • @Data:是个组合注解,包含上面的注解的功能

                                                        • @NoArgsConstructor:提供一个无参构造函数

                                                        • @AllArgsConstructor:提供一个包含所有参数的构造函数

                                                          @Data
                                                          @AllArgsConstructor
                                                          @NoArgsConstructor
                                                          public class User { private Long id;
                                                              private String name;
                                                              private String password;
                                                              private Integer age;
                                                              private String tel;
                                                          }
                                                          

                                                          2.3 分页功能

                                                          基础的增删改查就已经学习完了,接下来要学习在MP中如何实现分页功能

                                                          分页查询使用的方法是:

                                                          IPage selectPage(IPage page, Wrapper queryWrapper)
                                                          
                                                          • IPage:用来构建分页查询条件

                                                            • 第几页
                                                            • 一页显示多少个数据条数
                                                            • Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null

                                                            • IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage

                                                              IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page。

                                                              步骤1:调用方法传入参数获取返回值

                                                              @SpringBootTest
                                                              class Mybatisplus01QuickstartApplicationTests { @Autowired
                                                                  private UserDao userDao;
                                                                  
                                                                  //分页查询
                                                                  @Test
                                                                  void testSelectPage(){ //1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
                                                                      IPage page=new Page<>(1,3);
                                                                      //2 执行分页查询
                                                                      userDao.selectPage(page,null);
                                                                      //3 获取分页结果
                                                                      System.out.println("当前页码值:"+page.getCurrent());
                                                                      System.out.println("每页显示数:"+page.getSize());
                                                                      System.out.println("一共多少页:"+page.getPages());
                                                                      System.out.println("一共多少条数据:"+page.getTotal());
                                                                      System.out.println("数据:"+page.getRecords());
                                                                  }
                                                              }
                                                              

                                                              步骤2:设置分页拦截器

                                                              这个拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可。

                                                              @Configuration
                                                              public class MybatisPlusConfig { @Bean
                                                                  public MybatisPlusInterceptor mybatisPlusInterceptor(){ //1 创建MybatisPlusInterceptor拦截器对象
                                                                      MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
                                                                      //2 拦截器对象内置添加分页拦截器
                                                                      mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
                                                                      return mpInterceptor;
                                                                  }
                                                              }
                                                              

                                                              3. DQL编程控制

                                                              查询是非常重要的也是非常复杂的操作,这节我们主要学习的内容有:

                                                              • 条件查询方式

                                                              • 查询投影

                                                              • 查询条件设定

                                                              • 字段映射与表名映射

                                                                3.1 条件查询

                                                                3.1.1 条件查询的类
                                                                • MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。

                                                                  这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper类,这个类就是用来构建查询条件的,如下图所示:

                                                                  那么条件查询如何使用Wrapper来构建呢?

                                                                  3.1.2 环境构建
                                                                  • 测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以接下来我们把这个日志处理下:

                                                                    • 取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:
                                                                        • 取消MybatisPlus启动banner图标

                                                                          application.yml添加如下内容:

                                                                          # mybatis-plus日志控制台输出
                                                                          mybatis-plus:
                                                                            configuration:
                                                                              log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
                                                                            global-config:
                                                                              banner: off # 关闭mybatisplus启动图标
                                                                          
                                                                            • 取消SpringBoot的log打印

                                                                              application.yml添加如下内容:

                                                                              spring:
                                                                                main:
                                                                                  banner-mode: off # 关闭SpringBoot启动图标(banner)
                                                                              

                                                                              解决控制台打印日志过多的相关操作可以不用去做,一般会被用来方便我们查看程序运行的结果。

                                                                              3.1.3 构建条件查询

                                                                              在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式,

                                                                              1. 先来看第一种:QueryWrapper
                                                                              @SpringBootTest
                                                                              class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                  private UserDao userDao;
                                                                                  
                                                                                  @Test
                                                                                  void testGetAll(){ QueryWrapper qw = new QueryWrapper();
                                                                                      qw.lt("age",18);
                                                                                      List userList = userDao.selectList(qw);
                                                                                      System.out.println(userList);
                                                                                  }
                                                                              }
                                                                              
                                                                              • lt: 小于(<) ,最终的sql语句为
                                                                                SELECT id,name,password,age,tel FROM user WHERE (age < ?)
                                                                                

                                                                                第一种方式介绍完后,有个小问题就是在写条件的时候,容易出错,比如age写错,就会导致查询不成功

                                                                                1. 接着来看第二种:QueryWrapper的基础上使用lambda
                                                                                @SpringBootTest
                                                                                class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                    private UserDao userDao;
                                                                                    
                                                                                    @Test
                                                                                    void testGetAll(){ QueryWrapper qw = new QueryWrapper();
                                                                                        qw.lambda().lt(User::getAge, 10);//添加条件
                                                                                        List userList = userDao.selectList(qw);
                                                                                        System.out.println(userList);
                                                                                    }
                                                                                }
                                                                                
                                                                                • User::getAget,为lambda表达式中的,类名::方法名,最终的sql语句为:
                                                                                  SELECT id,name,password,age,tel FROM user WHERE (age < ?)
                                                                                  

                                                                                  **注意:**构建LambdaQueryWrapper的时候泛型不能省。

                                                                                  此时我们再次编写条件的时候,就不会存在写错名称的情况,但是qw后面多了一层lambda()调用

                                                                                  1. 接着来看第三种:LambdaQueryWrapper
                                                                                  @SpringBootTest
                                                                                  class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                      private UserDao userDao;
                                                                                      
                                                                                      @Test
                                                                                      void testGetAll(){ LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                          lqw.lt(User::getAge, 10);
                                                                                          List userList = userDao.selectList(lqw);
                                                                                          System.out.println(userList);
                                                                                      }
                                                                                  }
                                                                                  

                                                                                  这种方式就解决了上一种方式所存在的问题。

                                                                                  3.1.4 多条件构建

                                                                                  刚才都是一个条件,那如果有多个条件该如何构建呢?

                                                                                  1. 需求:查询数据库表中,年龄在10岁到30岁之间的用户信息
                                                                                  @SpringBootTest
                                                                                  class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                      private UserDao userDao;
                                                                                      
                                                                                      @Test
                                                                                      void testGetAll(){ LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                          lqw.lt(User::getAge, 30);
                                                                                          lqw.gt(User::getAge, 10);//大于 > List userList = userDao.selectList(lqw);
                                                                                          System.out.println(userList);
                                                                                      }
                                                                                  }
                                                                                  
                                                                                  • 构建多条件的时候,可以支持链式编程
                                                                                    LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                    lqw.lt(User::getAge, 30).gt(User::getAge, 10);
                                                                                    List userList = userDao.selectList(lqw);
                                                                                    System.out.println(userList);
                                                                                    
                                                                                    1. 需求:查询数据库表中,年龄小于10或年龄大于30的数据
                                                                                    @SpringBootTest
                                                                                    class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                        private UserDao userDao;
                                                                                        
                                                                                        @Test
                                                                                        void testGetAll(){ LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                            lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
                                                                                            List userList = userDao.selectList(lqw);
                                                                                            System.out.println(userList);
                                                                                        }
                                                                                    }
                                                                                    
                                                                                    • or()就相当于我们sql语句中的or关键字,不加默认是and,最终的sql语句为:
                                                                                      SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
                                                                                      
                                                                                      3.1.5 null判定

                                                                                      先来看一张图,

                                                                                      • 我们在做条件查询的时候,一般会有很多条件可以供用户进行选择查询。

                                                                                      • 这些条件用户可以选择使用也可以选择不使用,比如我要查询价格在8000以上的手机

                                                                                      • 在输入条件的时候,价格有一个区间范围,按照需求只需要在第一个价格输入框中输入8000

                                                                                      • 后台在做价格查询的时候,一般会让 price>值1 and price <值2

                                                                                      • 因为前端没有输入值2,所以如果不处理的话,就会出现 price>8000 and price < null问题

                                                                                      • 这个时候查询的结果就会出问题,具体该如何解决?

                                                                                        思考第一个问题:后台如果想接收前端的两个数据,该如何接收?

                                                                                        我们可以使用两个简单数据类型,也可以使用一个模型类,但是User类中目前只有一个age属性,如:

                                                                                        @Data
                                                                                        public class User { private Long id;
                                                                                            private String name;
                                                                                            private String password;
                                                                                            private Integer age;
                                                                                            private String tel;
                                                                                        }
                                                                                        

                                                                                        使用一个age属性,如何去接收页面上的两个值呢?这个时候我们有两个解决方案

                                                                                        方案一:添加属性age2,这种做法可以但是会影响到原模型类的属性内容

                                                                                        @Data
                                                                                        public class User { private Long id;
                                                                                            private String name;
                                                                                            private String password;
                                                                                            private Integer age;
                                                                                            private String tel;
                                                                                            @TableField(exist = false)
                                                                                            private Integer age2;
                                                                                        }
                                                                                        

                                                                                        方案二:新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性。

                                                                                        @Data
                                                                                        public class User { private Long id;
                                                                                            private String name;
                                                                                            private String password;
                                                                                            private Integer age;
                                                                                            private String tel;
                                                                                        }
                                                                                        @Data
                                                                                        public class UserQuery extends User { private Integer age2;
                                                                                        }
                                                                                        

                                                                                        现在来实现刚才的需求

                                                                                        @SpringBootTest
                                                                                        class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                            private UserDao userDao;
                                                                                            
                                                                                            @Test
                                                                                            void testGetAll(){ //模拟页面传递过来的查询数据
                                                                                                UserQuery uq = new UserQuery();
                                                                                                uq.setAge(10);
                                                                                                uq.setAge2(30);
                                                                                                LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                                if(null != uq.getAge2()){ lqw.lt(User::getAge, uq.getAge2());
                                                                                                }
                                                                                                if( null != uq.getAge()) { lqw.gt(User::getAge, uq.getAge());
                                                                                                }
                                                                                                List userList = userDao.selectList(lqw);
                                                                                                System.out.println(userList);
                                                                                            }
                                                                                        }
                                                                                        

                                                                                        上面的写法可以完成条件为非空的判断,但是问题很明显,如果条件多的话,每个条件都需要判断,代码量就比较大,来看MP给我们提供的简化方式:

                                                                                        @SpringBootTest
                                                                                        class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                            private UserDao userDao;
                                                                                            
                                                                                            @Test
                                                                                            void testGetAll(){ //模拟页面传递过来的查询数据
                                                                                                UserQuery uq = new UserQuery();
                                                                                                uq.setAge(10);
                                                                                                uq.setAge2(30);
                                                                                                LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                                lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
                                                                                                lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
                                                                                                List userList = userDao.selectList(lqw);
                                                                                                System.out.println(userList);
                                                                                            }
                                                                                        }
                                                                                        
                                                                                        • lt()方法

                                                                                          condition为boolean类型,返回true,则添加条件,返回false则不添加条件

                                                                                          3.2 查询投影

                                                                                          3.2.1 查询指定字段

                                                                                          目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据。

                                                                                          select(...)方法用来设置查询的字段列,可以设置多个,

                                                                                          @SpringBootTest
                                                                                          class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                              private UserDao userDao;
                                                                                              
                                                                                              @Test
                                                                                              void testGetAll(){ LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                                  lqw.select(User::getId,User::getName,User::getAge);
                                                                                                  List userList = userDao.selectList(lqw);
                                                                                                  System.out.println(userList);
                                                                                              }
                                                                                          }
                                                                                          
                                                                                          • 最终的sql语句为:
                                                                                            SELECT id,name,age FROM user
                                                                                            
                                                                                            • 如果使用的不是lambda,就需要手动指定字段
                                                                                              @SpringBootTest
                                                                                              class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                                  private UserDao userDao;
                                                                                                  
                                                                                                  @Test
                                                                                                  void testGetAll(){ QueryWrapper lqw = new QueryWrapper();
                                                                                                      lqw.select("id","name","age","tel");
                                                                                                      List userList = userDao.selectList(lqw);
                                                                                                      System.out.println(userList);
                                                                                                  }
                                                                                              }
                                                                                              
                                                                                                • 最终的sql语句为:SELECT id,name,age,tel FROM user
                                                                                                  3.2.2 聚合函数查询

                                                                                                  实现:

                                                                                                  @SpringBootTest
                                                                                                  class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                                      private UserDao userDao;
                                                                                                      
                                                                                                      @Test
                                                                                                      void testGetAll(){ QueryWrapper lqw = new QueryWrapper();
                                                                                                          //lqw.select("count(*) as count");
                                                                                                          //SELECT count(*) as count FROM user
                                                                                                          //lqw.select("max(age) as maxAge");
                                                                                                          //SELECT max(age) as maxAge FROM user
                                                                                                          //lqw.select("min(age) as minAge");
                                                                                                          //SELECT min(age) as minAge FROM user
                                                                                                          //lqw.select("sum(age) as sumAge");
                                                                                                          //SELECT sum(age) as sumAge FROM user
                                                                                                          lqw.select("avg(age) as avgAge");
                                                                                                          //SELECT avg(age) as avgAge FROM user
                                                                                                          List> userList = userDao.selectMaps(lqw);
                                                                                                          System.out.println(userList);
                                                                                                      }
                                                                                                  }
                                                                                                  

                                                                                                  为了在做结果封装的时候能够更简单,我们将上面的聚合函数都起了个名称,方面后期来获取这些数据

                                                                                                  3.2.3 分组查询

                                                                                                  需求:分组查询,完成 group by的查询使用

                                                                                                  @SpringBootTest
                                                                                                  class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                                      private UserDao userDao;
                                                                                                      
                                                                                                      @Test
                                                                                                      void testGetAll(){ QueryWrapper lqw = new QueryWrapper();
                                                                                                          lqw.select("count(*) as count,tel");
                                                                                                          lqw.groupBy("tel");
                                                                                                          List> list = userDao.selectMaps(lqw);
                                                                                                          System.out.println(list);
                                                                                                      }
                                                                                                  }
                                                                                                  
                                                                                                  • groupBy为分组,最终的sql语句为
                                                                                                    SELECT count(*) as count,tel FROM user GROUP BY tel
                                                                                                    

                                                                                                    注意:

                                                                                                    • 聚合与分组查询,无法使用lambda表达式来完成

                                                                                                    • MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现

                                                                                                      3.3 查询条件

                                                                                                      前面我们只使用了lt()和gt(),除了这两个方法外,MP还封装了很多条件对应的方法,这一节我们重点把MP提供的查询条件方法进行学习下。

                                                                                                      MP的查询条件有很多:

                                                                                                      • 范围匹配(> 、 = 、between)

                                                                                                      • 模糊匹配(like)

                                                                                                      • 空判定(null)

                                                                                                      • 包含性匹配(in)

                                                                                                      • 分组(group)

                                                                                                      • 排序(order)

                                                                                                      • ……

                                                                                                        3.3.1 等值查询

                                                                                                        需求:根据用户名和密码查询用户信息

                                                                                                        @SpringBootTest
                                                                                                        class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                                            private UserDao userDao;
                                                                                                            
                                                                                                            @Test
                                                                                                            void testGetAll(){ LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                                                lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
                                                                                                                User loginUser = userDao.selectOne(lqw);
                                                                                                                System.out.println(loginUser);
                                                                                                            }
                                                                                                        }
                                                                                                        
                                                                                                        • eq(): 相当于 =,对应的sql语句为
                                                                                                          SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
                                                                                                          
                                                                                                          • selectList:查询结果为多个或者单个

                                                                                                          • selectOne:查询结果为单个

                                                                                                            3.3.2 范围查询

                                                                                                            需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询

                                                                                                            @SpringBootTest
                                                                                                            class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                                                private UserDao userDao;
                                                                                                                
                                                                                                                @Test
                                                                                                                void testGetAll(){ LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                                                    lqw.between(User::getAge, 10, 30);
                                                                                                                    //SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
                                                                                                                    List userList = userDao.selectList(lqw);
                                                                                                                    System.out.println(userList);
                                                                                                                }
                                                                                                            }
                                                                                                            
                                                                                                            • gt():大于(>)

                                                                                                            • ge():大于等于(>=)

                                                                                                            • lt():小于(<)

                                                                                                            • lte():小于等于(<=)

                                                                                                            • between():between ? and ?

                                                                                                              3.3.3 模糊查询

                                                                                                              需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询

                                                                                                              @SpringBootTest
                                                                                                              class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                                                  private UserDao userDao;
                                                                                                                  
                                                                                                                  @Test
                                                                                                                  void testGetAll(){ LambdaQueryWrapper lqw = new LambdaQueryWrapper();
                                                                                                                      lqw.likeLeft(User::getName, "J");
                                                                                                                      //SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
                                                                                                                      List userList = userDao.selectList(lqw);
                                                                                                                      System.out.println(userList);
                                                                                                                  }
                                                                                                              }
                                                                                                              
                                                                                                              • like():前后加百分号,如 %J%

                                                                                                              • likeLeft():前面加百分号,如 %J

                                                                                                              • likeRight():后面加百分号,如 J%

                                                                                                                3.3.4 排序查询

                                                                                                                需求:查询所有数据,然后按照id降序

                                                                                                                @SpringBootTest
                                                                                                                class Mybatisplus02DqlApplicationTests { @Autowired
                                                                                                                    private UserDao userDao;
                                                                                                                    
                                                                                                                    @Test
                                                                                                                    void testGetAll(){ LambdaQueryWrapper lwq = new LambdaQueryWrapper<>();
                                                                                                                        /**
                                                                                                                         * condition :条件,返回boolean,
                                                                                                                         		当condition为true,进行排序,如果为false,则不排序
                                                                                                                         * isAsc:是否为升序,true为升序,false为降序
                                                                                                                         * columns:需要操作的列
                                                                                                                         */
                                                                                                                        lwq.orderBy(true,false, User::getId);
                                                                                                                        System.out.println(userDao.selectList(lqw));
                                                                                                                    }
                                                                                                                }
                                                                                                                

                                                                                                                除了上面演示的这种实现方式,还有很多其他的排序方法可以被调用,如图:

                                                                                                                • orderBy排序

                                                                                                                  • condition:条件,true则添加排序,false则不添加排序
                                                                                                                    • isAsc:是否为升序,true升序,false降序
                                                                                                                      • columns:排序字段,可以有多个
                                                                                                                      • orderByAsc/Desc(单个column):按照指定字段进行升序/降序

                                                                                                                      • orderByAsc/Desc(多个column):按照多个字段进行升序/降序

                                                                                                                      • orderByAsc/Desc

                                                                                                                        • condition:条件,true添加排序,false不添加排序
                                                                                                                          • 多个columns:按照多个字段进行排序

                                                                                                                            3.4 映射匹配兼容性

                                                                                                                            前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型类:

                                                                                                                            之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。

                                                                                                                            那么问题就来了:

                                                                                                                            问题1:表字段与编码属性设计不同步

                                                                                                                            当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?

                                                                                                                            MP给我们提供了一个注解@TableField,使用该注解可以实现模型类属性名和表的列名之间的映射关系

                                                                                                                            问题2:编码中添加了数据库中未定义的属性

                                                                                                                            当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:

                                                                                                                            Unknown column ‘多出来的字段名称’ in ‘field list’

                                                                                                                            具体的解决方案用到的还是@TableField注解,它有一个属性叫exist,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。

                                                                                                                            问题3:采用默认查询开放了更多的字段查看权限

                                                                                                                            查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。解决方案是@TableField注解的一个属性叫select,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。

                                                                                                                            @TableField

                                                                                                                            名称@TableField
                                                                                                                            类型属性注解
                                                                                                                            位置模型类属性定义上方
                                                                                                                            作用设置当前属性对应的数据库表中的字段关系
                                                                                                                            相关属性value(默认):设置数据库表字段名称 exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并使用 select:设置属性是否参与查询,此属性与select()映射配置不冲突

                                                                                                                            问题4:表名与编码开发设计不同步

                                                                                                                            该问题主要是表的名称和模型类的名称不一致,导致查询失败,这个时候通常会报如下错误信息:

                                                                                                                            Table ‘databaseName.tableName’ doesn’t exist,翻译过来就是数据库中的表不存在。

                                                                                                                            解决方案是使用MP提供的另外一个注解@TableName来设置表与模型类之间的对应关系。

                                                                                                                            @TableName

                                                                                                                            名称@TableName
                                                                                                                            类型类注解
                                                                                                                            位置模型类定义上方
                                                                                                                            作用设置当前类对应于数据库表关系
                                                                                                                            相关属性value(默认):设置数据库表名称

                                                                                                                            4. DML编程控制(增删改)

                                                                                                                            查询相关的操作我们已经介绍完了,紧接着我们需要对另外三个— —增删改进行内容的讲解。挨个来说明下,首先是新增(insert)中的内容。

                                                                                                                            4.1 id生成策略控制(增)

                                                                                                                            前面我们在新增的时候留了一个问题,就是新增成功后,主键ID是一个很长串的内容,我们更想要的是按照数据库表字段进行自增长,在解决这个问题之前,我们先来分析下ID该如何选择:

                                                                                                                            • 不同的表应用不同的id生成策略

                                                                                                                              • 日志:自增(1,2,3,4,……)
                                                                                                                                • 购物订单:特殊规则(FQ23948AK3843)
                                                                                                                                  • 外卖单:关联地区日期等信息(10 04 20200314 34 91)
                                                                                                                                    • 关系表:可省略id
                                                                                                                                      • ……

                                                                                                                                        不同的业务采用的ID生成方式应该是不一样的,那么在MP中都提供了哪些主键生成策略,以及我们该如何进行选择?

                                                                                                                                        在这里我们又需要用到MP的一个注解叫@TableId

                                                                                                                                        @TableId

                                                                                                                                        名称@TableId
                                                                                                                                        类型属性注解
                                                                                                                                        位置模型类中用于表示主键的属性定义上方
                                                                                                                                        作用设置当前类中主键属性的生成策略
                                                                                                                                        相关属性value(默认):设置数据库表主键名称 type:设置主键属性的生成策略,值查照IdType的枚举值

                                                                                                                                        IdType

                                                                                                                                        从源码中可以了解到:

                                                                                                                                        • AUTO:自动递增策略
                                                                                                                                          • NONE: 不设置id生成策略

                                                                                                                                          • INPUT:用户手工输入id

                                                                                                                                          • ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型)

                                                                                                                                          • ASSIGN_UUID:以UUID生成算法作为id生成策略

                                                                                                                                            • pojo类中主键对应的属性的类型只能是String类型;数据库表中的主键类型为varchar(),且长度要大于32,因为UUID生成的主键为32位,如果长度小的话就会导致插入失败。
                                                                                                                                            • 其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。

                                                                                                                                              接下来我们来聊一聊雪花算法:

                                                                                                                                              雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。其生成的结果是一个64bit大小整数,它的结构如下图:

                                                                                                                                              1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用正数,所以最高位固定为0。

                                                                                                                                              2. 41bit-时间戳,用来记录时间戳,毫秒级

                                                                                                                                              3. 10bit-工作机器id,用来记录工作机器id:其中高位5bit是数据中心ID,其取值范围0-31;低位5bit是工作节点,ID其取值范围0-31,两个组合起来最多可以容纳1024个节点

                                                                                                                                              4. 序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID

                                                                                                                                              ID生成策略对比

                                                                                                                                              介绍了这些主键ID的生成策略,我们以后该用哪个呢?

                                                                                                                                              • NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂

                                                                                                                                              • AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用

                                                                                                                                              • ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢

                                                                                                                                              • ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键

                                                                                                                                              • 综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。

                                                                                                                                                拓展:

                                                                                                                                                分布式ID是什么?

                                                                                                                                                • 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储

                                                                                                                                                • 比如订单表就有可能被存储在不同的服务器上

                                                                                                                                                • 如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突

                                                                                                                                                • 这个时候就需要一个全局唯一ID,这个ID就是分布式ID。

                                                                                                                                                  简化配置

                                                                                                                                                  前面我们已经完成了表关系映射、数据库主键策略的设置,接下来对于这两个内容的使用,再讲下他们的简化配置:

                                                                                                                                                  模型类主键策略设置

                                                                                                                                                  对于主键ID的策略已经介绍完,但是如果要在项目中的每一个模型类上都需要使用相同的生成策略,如:

                                                                                                                                                  我们只需要在配置文件中添加如下内容,就能让所有的模型类都可以使用该主键ID策略

                                                                                                                                                  mybatis-plus:
                                                                                                                                                    global-config:
                                                                                                                                                      db-config:
                                                                                                                                                      	id-type: assign_id
                                                                                                                                                  

                                                                                                                                                  配置完成后,每个模型类的主键ID策略都将成为assign_id.

                                                                                                                                                  数据库表与模型类的映射关系

                                                                                                                                                  MP会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以tbl_开头,那么我们就需要将所有的模型类上添加@TableName,如:

                                                                                                                                                  简化方式为在配置文件中配置如下内容:

                                                                                                                                                  mybatis-plus:
                                                                                                                                                    global-config:
                                                                                                                                                      db-config:
                                                                                                                                                      	table-prefix: tbl_
                                                                                                                                                  

                                                                                                                                                  设置表的前缀内容,这样MP就会拿 tbl_加上模型类的首字母小写,就刚好组装成数据库的表名。

                                                                                                                                                  4.2 多记录操作(删、查)

                                                                                                                                                  场景:批量删除清空购物车

                                                                                                                                                  按照id集合批量删除

                                                                                                                                                  int deleteBatchIds(@Param(Constants.COLLECTION) Collection idList);
                                                                                                                                                  

                                                                                                                                                  除了按照id集合进行批量删除,也可以按照id集合进行批量查询

                                                                                                                                                  List selectBatchIds(@Param(Constants.COLLECTION) Collection idList);
                                                                                                                                                  

                                                                                                                                                  4.3 逻辑删除(删 -> 改)

                                                                                                                                                  对于删除操作业务问题来说有:

                                                                                                                                                  • 物理删除:业务数据从数据库中丢弃,执行的是delete操作

                                                                                                                                                  • 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的其实是update操作

                                                                                                                                                    MP中逻辑删除具体该如何实现?

                                                                                                                                                    步骤1:修改数据库表添加deleted列

                                                                                                                                                    字段名可以任意,内容也可以自定义,比如0代表正常,1代表删除,可以在添加列的同时设置其默认值为0正常。

                                                                                                                                                    步骤2:实体类添加属性

                                                                                                                                                    标识新增的字段为逻辑删除字段,使用@TableLogic

                                                                                                                                                    @Data
                                                                                                                                                    //@TableName("tbl_user") 可以不写是因为配置了全局配置
                                                                                                                                                    public class User { @TableId(type = IdType.ASSIGN_UUID)
                                                                                                                                                        private String id;
                                                                                                                                                        private String name;
                                                                                                                                                        @TableField(value="pwd",select=false)
                                                                                                                                                        private String password;
                                                                                                                                                        private Integer age;
                                                                                                                                                        private String tel;
                                                                                                                                                        @TableField(exist=false)
                                                                                                                                                        private Integer online;
                                                                                                                                                        @TableLogic(value="0",delval="1")
                                                                                                                                                        //value为正常数据的值,delval为删除数据的值
                                                                                                                                                        private Integer deleted;
                                                                                                                                                    }
                                                                                                                                                    

                                                                                                                                                    步骤3:运行删除方法

                                                                                                                                                    @SpringBootTest
                                                                                                                                                    class Mybatisplus03DqlApplicationTests { @Autowired
                                                                                                                                                        private UserDao userDao;
                                                                                                                                                    	
                                                                                                                                                        @Test
                                                                                                                                                        void testDelete(){ userDao.deleteById(1L);
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                    

                                                                                                                                                    从测试结果来看,逻辑删除最后走的是update操作,会将指定的字段修改成删除状态对应的值。

                                                                                                                                                    逻辑删除,对查询也是有影响的

                                                                                                                                                    • 执行查询操作
                                                                                                                                                      @SpringBootTest
                                                                                                                                                      class Mybatisplus03DqlApplicationTests { @Autowired
                                                                                                                                                          private UserDao userDao;
                                                                                                                                                      	
                                                                                                                                                          @Test
                                                                                                                                                          void testFind(){ System.out.println(userDao.selectList(null));
                                                                                                                                                          }
                                                                                                                                                      }
                                                                                                                                                      

                                                                                                                                                      运行测试,会发现打印出来的sql语句中会多一个查询条件,如:

                                                                                                                                                      可想而知,MP的逻辑删除会将所有的查询都添加一个未被删除的条件(where deleted = 0),也就是已经被删除的数据是不应该被查询出来的。

                                                                                                                                                      • 如果还是想把已经删除的数据都查询出来,就需要在Dao接口中手写sql
                                                                                                                                                        @Mapper
                                                                                                                                                        public interface UserDao extends BaseMapper { //查询所有数据包含已经被删除的数据
                                                                                                                                                            @Select("select * from tbl_user")
                                                                                                                                                            public List selectAll();
                                                                                                                                                        }
                                                                                                                                                        
                                                                                                                                                        • 如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加@TableLogic注解,优化方法:

                                                                                                                                                          在配置文件中添加全局配置,如下:

                                                                                                                                                          mybatis-plus:
                                                                                                                                                            global-config:
                                                                                                                                                              db-config:
                                                                                                                                                                # 逻辑删除字段名
                                                                                                                                                                logic-delete-field: deleted
                                                                                                                                                                # 逻辑删除字面值:未删除为0
                                                                                                                                                                logic-not-delete-value: 0
                                                                                                                                                                # 逻辑删除字面值:删除为1
                                                                                                                                                                logic-delete-value: 1
                                                                                                                                                          

                                                                                                                                                          介绍完逻辑删除,逻辑删除的本质为:

                                                                                                                                                          逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。

                                                                                                                                                          执行的SQL语句为:

                                                                                                                                                          UPDATE tbl_user SET deleted=1 where id = ? AND deleted=0
                                                                                                                                                          

                                                                                                                                                          @TableLogic

                                                                                                                                                          名称@TableLogic
                                                                                                                                                          类型属性注解
                                                                                                                                                          位置模型类中用于表示删除字段的属性定义上方
                                                                                                                                                          作用标识该字段为进行逻辑删除的字段
                                                                                                                                                          相关属性value:逻辑未删除值 delval:逻辑删除值

                                                                                                                                                          4.4 乐观锁(改)

                                                                                                                                                          业务并发现象带来的问题:秒杀

                                                                                                                                                          • 假如有100个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,如何保证不会出现超买或者重复卖

                                                                                                                                                          • 对于这一类问题,其实有很多的解决方案可以使用

                                                                                                                                                          • 第一个最先想到的就是锁,锁在一台服务器中是可以解决的,但是如果在多台服务器下锁就没有办法控制,比如12306有两台服务器在进行卖票,在两台服务器上都添加锁的话,那也有可能会导致在同一时刻有两个线程在进行卖票,还是会出现并发问题

                                                                                                                                                          • 我们接下来介绍的这种方式是针对于小型企业的解决方案,因为数据库本身的性能就是个瓶颈,如果对其并发量超过2000以上的就需要考虑其他的解决方案了。

                                                                                                                                                            简单来说,乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。

                                                                                                                                                            乐观锁的实现方式:

                                                                                                                                                            • 数据库表中添加version列,比如默认值给1

                                                                                                                                                            • 第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1

                                                                                                                                                            • 第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1

                                                                                                                                                            • 第一个线程执行更新时,set version = newVersion where version = oldVersion

                                                                                                                                                              • newVersion = version+1 [2]
                                                                                                                                                                • oldVersion = version [1]
                                                                                                                                                                • 第二个线程执行更新时,set version = newVersion where version = oldVersion

                                                                                                                                                                  • newVersion = version+1 [2]
                                                                                                                                                                    • oldVersion = version [1]
                                                                                                                                                                    • 假如这两个线程都来更新数据,第一个和第二个线程都可能先执行

                                                                                                                                                                      • 假如第一个线程先执行更新,会把version改为2,
                                                                                                                                                                        • 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第二个线程会修改失败
                                                                                                                                                                          • 假如第二个线程先执行更新,会把version改为2,
                                                                                                                                                                            • 第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第一个线程会修改失败
                                                                                                                                                                              • 不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。

                                                                                                                                                            实现步骤:

                                                                                                                                                            分析完步骤后,具体的实现步骤如下:

                                                                                                                                                            步骤1:数据库表添加列

                                                                                                                                                            列名可以任意,比如使用version,给列设置默认值为1

                                                                                                                                                            步骤2:在模型类中添加对应的属性

                                                                                                                                                            根据添加的字段列名,在模型类中添加对应的属性值

                                                                                                                                                            @Data
                                                                                                                                                            //@TableName("tbl_user") 可以不写是因为配置了全局配置
                                                                                                                                                            public class User { @TableId(type = IdType.ASSIGN_UUID)
                                                                                                                                                                private String id;
                                                                                                                                                                private String name;
                                                                                                                                                                @TableField(value="pwd",select=false)
                                                                                                                                                                private String password;
                                                                                                                                                                private Integer age;
                                                                                                                                                                private String tel;
                                                                                                                                                                @TableField(exist=false)
                                                                                                                                                                private Integer online;
                                                                                                                                                                private Integer deleted;
                                                                                                                                                                @Version
                                                                                                                                                                private Integer version;
                                                                                                                                                            }
                                                                                                                                                            

                                                                                                                                                            步骤3:添加乐观锁的拦截器

                                                                                                                                                            @Configuration
                                                                                                                                                            public class MpConfig { @Bean
                                                                                                                                                                public MybatisPlusInterceptor mpInterceptor() { //1.定义Mp拦截器
                                                                                                                                                                    MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
                                                                                                                                                                    //2.添加乐观锁拦截器
                                                                                                                                                                    mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
                                                                                                                                                                    return mpInterceptor;
                                                                                                                                                                }
                                                                                                                                                            }
                                                                                                                                                            

                                                                                                                                                            步骤4:执行更新操作

                                                                                                                                                            pojo类对象必须要携带version数据,否则乐观锁无法启用

                                                                                                                                                            @SpringBootTest
                                                                                                                                                            class Mybatisplus03DqlApplicationTests { @Autowired
                                                                                                                                                                private UserDao userDao;
                                                                                                                                                            	
                                                                                                                                                                @Test
                                                                                                                                                                void testUpdate(){ User user = new User();
                                                                                                                                                                    user.setId(3L);
                                                                                                                                                                    user.setName("Jock666");
                                                                                                                                                                    user.setVersion(1);
                                                                                                                                                                    userDao.updateById(user);
                                                                                                                                                                }
                                                                                                                                                            }
                                                                                                                                                            

                                                                                                                                                            要想实现乐观锁,首先第一步应该是拿到表中的version,然后拿version当条件在将version加1更新回到数据库表中,所以我们在查询的时候,需要对其进行查询

                                                                                                                                                            @SpringBootTest
                                                                                                                                                            class Mybatisplus03DqlApplicationTests { @Autowired
                                                                                                                                                                private UserDao userDao;
                                                                                                                                                            	
                                                                                                                                                                @Test
                                                                                                                                                                void testUpdate(){ //1.先通过要修改的数据id将当前数据查询出来
                                                                                                                                                                    User user = userDao.selectById(3L);
                                                                                                                                                                    //2.将要修改的属性逐一设置进去
                                                                                                                                                                    user.setName("Jock888");
                                                                                                                                                                    userDao.updateById(user);
                                                                                                                                                                }
                                                                                                                                                            }
                                                                                                                                                            

                                                                                                                                                            案例:来模拟一种加锁的情况,看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。

                                                                                                                                                            @SpringBootTest
                                                                                                                                                            class Mybatisplus03DqlApplicationTests { @Autowired
                                                                                                                                                                private UserDao userDao;
                                                                                                                                                            	
                                                                                                                                                                @Test
                                                                                                                                                                void testUpdate(){ //1.先通过要修改的数据id将当前数据查询出来
                                                                                                                                                                    User user = userDao.selectById(3L);     //version=3
                                                                                                                                                                    User user2 = userDao.selectById(3L);    //version=3
                                                                                                                                                                    user2.setName("Jock aaa");
                                                                                                                                                                    userDao.updateById(user2);              //version=>4
                                                                                                                                                                    user.setName("Jock bbb");
                                                                                                                                                                    userDao.updateById(user);               //verion=3?条件还成立吗?
                                                                                                                                                                }
                                                                                                                                                            }
                                                                                                                                                            

                                                                                                                                                            运行程序,分析结果:

                                                                                                                                                            5. 代码生成器

                                                                                                                                                            观察我们之前写的代码,会发现其中也会有很多重复内容,比如:

                                                                                                                                                            那我们就想,如果我想做一个Book模块的开发,是不是只需要将红色部分的内容全部更换成Book即可,如:

                                                                                                                                                            所以我们会发现,做任何模块的开发,对于这段代码,基本上都是对红色部分的调整,所以我们把去掉红色内容的东西称之为模板,红色部分称之为参数,以后只需要传入不同的参数,就可以根据模板创建出不同模块的dao代码。

                                                                                                                                                            除了Dao可以抽取模块,其实我们常见的类都可以进行抽取,只要他们有公共部分即可。再来看下模型类的模板:

                                                                                                                                                            • ① 可以根据数据库表的表名来填充

                                                                                                                                                            • ② 可以根据用户的配置来生成ID生成策略,需要开发者自己完成

                                                                                                                                                            • ③到⑨可以根据数据库表字段名称来填充

                                                                                                                                                              所以只要我们知道是对哪张表进行代码生成,这些内容我们都可以进行填充。

                                                                                                                                                              分析完后,我们会发现,要想完成代码自动生成,我们需要有以下内容:

                                                                                                                                                              • 模板: MyBatisPlus提供,可以自己提供,但是麻烦,不建议

                                                                                                                                                              • 数据库相关配置:读取数据库获取表和字段信息

                                                                                                                                                              • 开发者自定义配置:手工配置,比如ID生成策略

                                                                                                                                                                5.1 代码生成器实现

                                                                                                                                                                环境配置:

                                                                                                                                                                1.pom.xml

                                                                                                                                                                 4.0.0  org.springframework.boot spring-boot-starter-parent 2.5.1  com.itheima mybatisplus_04_generator 0.0.1-SNAPSHOT  1.8     org.springframework.boot spring-boot-starter-web    com.baomidou mybatis-plus-boot-starter 3.4.1    com.alibaba druid 1.1.16    mysql mysql-connector-java runtime    org.springframework.boot spring-boot-starter-test test    org.projectlombok lombok 1.18.12    com.baomidou mybatis-plus-generator 3.4.1    org.apache.velocity velocity-engine-core 2.3      org.springframework.boot spring-boot-maven-plugin   

                                                                                                                                                                2.创建代码生成类

                                                                                                                                                                public class CodeGenerator { public static void main(String[] args) { //1.获取代码生成器的对象
                                                                                                                                                                        AutoGenerator autoGenerator = new AutoGenerator();
                                                                                                                                                                        //设置数据库相关配置
                                                                                                                                                                        DataSourceConfig dataSource = new DataSourceConfig();
                                                                                                                                                                        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
                                                                                                                                                                        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
                                                                                                                                                                        dataSource.setUsername("root");
                                                                                                                                                                        dataSource.setPassword("root");
                                                                                                                                                                        autoGenerator.setDataSource(dataSource);
                                                                                                                                                                        //设置全局配置
                                                                                                                                                                        GlobalConfig globalConfig = new GlobalConfig();
                                                                                                                                                                        globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java");    //设置代码生成位置
                                                                                                                                                                        globalConfig.setOpen(false);    //设置生成完毕后是否打开生成代码所在的目录
                                                                                                                                                                        globalConfig.setAuthor("wxy");    //设置作者
                                                                                                                                                                        globalConfig.setFileOverride(true);     //设置是否覆盖原始生成的文件
                                                                                                                                                                        globalConfig.setMapperName("%sDao");    //设置数据层接口名,%s为占位符,指代模块名称
                                                                                                                                                                        globalConfig.setIdType(IdType.ASSIGN_ID);   //设置Id生成策略
                                                                                                                                                                        autoGenerator.setGlobalConfig(globalConfig);
                                                                                                                                                                        //设置包名相关配置
                                                                                                                                                                        PackageConfig packageInfo = new PackageConfig();
                                                                                                                                                                        packageInfo.setParent("com.aaa");   //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
                                                                                                                                                                        packageInfo.setEntity("domain");    //设置实体类包名
                                                                                                                                                                        packageInfo.setMapper("dao");   //设置数据层包名
                                                                                                                                                                        autoGenerator.setPackageInfo(packageInfo);
                                                                                                                                                                        //策略设置
                                                                                                                                                                        StrategyConfig strategyConfig = new StrategyConfig();
                                                                                                                                                                        strategyConfig.setInclude("tbl_user");  //设置当前参与生成的表名,参数为可变参数
                                                                                                                                                                        strategyConfig.setTablePrefix("tbl_");  //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名  例如: User = tbl_user - tbl_
                                                                                                                                                                        strategyConfig.setRestControllerStyle(true);    //设置是否启用Rest风格
                                                                                                                                                                        strategyConfig.setVersionFieldName("version");  //设置乐观锁字段名
                                                                                                                                                                        strategyConfig.setLogicDeleteFieldName("deleted");  //设置逻辑删除字段名
                                                                                                                                                                        strategyConfig.setEntityLombokModel(true);  //设置是否启用lombok
                                                                                                                                                                        autoGenerator.setStrategy(strategyConfig);
                                                                                                                                                                        //2.执行生成操作
                                                                                                                                                                        autoGenerator.execute();
                                                                                                                                                                    }
                                                                                                                                                                }
                                                                                                                                                                

                                                                                                                                                                3.运行程序

                                                                                                                                                                至此代码生成器就已经完成工作,我们能快速根据数据库表来创建对应的类,简化我们的代码开发。

                                                                                                                                                                5.2 MP中Service的CRUD

                                                                                                                                                                回顾我们之前业务层代码的编写,编写接口和对应的实现类:

                                                                                                                                                                public interface UserService{}
                                                                                                                                                                @Service
                                                                                                                                                                public class UserServiceImpl implements UserService{}
                                                                                                                                                                

                                                                                                                                                                接口和实现类有了以后,需要在接口和实现类中声明方法

                                                                                                                                                                public interface UserService{public List findAll();
                                                                                                                                                                }
                                                                                                                                                                @Service
                                                                                                                                                                public class UserServiceImpl implements UserService{ @Autowired
                                                                                                                                                                    private UserDao userDao;
                                                                                                                                                                    
                                                                                                                                                                	public List findAll(){ return userDao.selectList(null);
                                                                                                                                                                    }
                                                                                                                                                                }
                                                                                                                                                                

                                                                                                                                                                MP看到上面的代码以后就说这些方法也是比较固定和通用的,所以MP抽取并提供了一个Service接口和实现类,分别是:IService和ServiceImpl,后者是对前者的一个具体实现。

                                                                                                                                                                以后我们自己写的Service就可以进行如下修改:

                                                                                                                                                                public interface UserService extends IService{}
                                                                                                                                                                @Service
                                                                                                                                                                public class UserServiceImpl extends ServiceImpl implements UserService{}
                                                                                                                                                                

                                                                                                                                                                修改以后的好处是,MP已经帮我们把业务层的一些基础的增删改查都已经实现了,可以直接进行使用。