MyBatisPlus

MyBatisPlus

  • MyBatisPlus简称mp,是mybatis的增强工具,在mybatis的基础上只做增强,不做修改
  • 特性
    1. 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
    2. 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
    3. 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
    4. 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
    5. 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
    6. 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
    7. 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
    8. 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
    9. 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
    10. 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
    11. 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
    12. 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 构建mybatis-plus-demo
    1. 导入依赖
          
              org.springframework.boot
              spring-boot-starter-parent
              2.4.3
              
          
      
          
      
              
                  com.baomidou
                  mybatis-plus-boot-starter
                  3.4.0
              
      
              
                  com.alibaba
                  druid-spring-boot-starter
                  1.1.17
              
      
              
                  mysql
                  mysql-connector-java
                  8.0.22
              
      
              
                  org.springframework.boot
                  spring-boot-starter-test
              
      
              
                  org.projectlombok
                  lombok
                  1.18.16
                  provided
              
      
          
      
    2. 新建application.properties文件
          server.port=80
          #这里记得配置下时区serverTimezone(东八区),否则可能会报错
          spring.datasource.url=jdbc:mysql:///mybatis-plus?useUnicode=true&\
          characterEncoding=utf8&serverTimezone=GMT%2B8
          spring.datasource.username=root
          spring.datasource.password=admin
          spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
          # 配置slq打印日志
          logging.level.cn.wolfcode.mp.mapper=debug
      
    3. 按照以往常规操作,我们做crud,需要编写mapper接口,并在mapper.xml中编写对应的sql语句,但是在mybatisPlus中,只需创建好对应的mapper接口,继承下 BaseMapper<实体类泛型(如:Employee)>,常规的crud都能在BaseMapper中找到,所以就无须我们在自己的mapper接口中写了(如:EmployeeMapper),具体操作如下
      • 当spring容器启动后,mybatisPlus就开始解析EmployeeMapper,上面指定了泛型Employee
      • 通过反射拿到类型Employee,然后得到其类名作为数据库表名,得到其属性作为数据库表中对应的列名
      • 将解析出来的数据根据调用的方法来拼接出不同的语句.
    4. mybatisPlus中的方法及注意点:
      • 常用注解:
        1. @TableName表名注解:指当前类映射数据库中哪一张表,默认实体类与表名一致,当不一致的使用时:如数据库表名为t_employee,实体类名为Employee,因为名字不一致,所以直接肯定是映射不到的,此时只需在实体类Employee上贴上@TableName("t_employee")即可,表示该实体类映射到t_employee这张表
        2. @TableId主键注解:标记当前属性映射表主键:如
              @TableName("employee")
              public class Employee {
                  //value="id"对应数据库的列名,IdType.AUTO表示自增长(还可以有其他的设置方式,如自己输入,详情可查看对应的api)
                  @TableId(value="id", type=IdType.AUTO)
                  private Long id;
              }
          
        3. @TableField字段注解(非主键):指定当前属性映射数据库表哪一列, 默认是跟属性名一致,如:
              //其中:exist属性表示当前属性是否映射数据库列,当我们是自定义的属性,就额外加的,不是和数据库对应的,那么就必须@TableField(exist = false)这样设置下,否则会报错
               @TableField(value="employename",exist = true)
              private String name;
          
      • updateById:根据id修改,等价于update employee set xxx=xxx where id = xxx
      • updateById的注意点:(它的set片段会根据传入的实体对象(如:employee)对应的属性是否为null来判断是否拼接)
        1. 当对应的属性为null时,那么set片段中就不会把对应的属性拼接上去(即:执行更新操作后,数据库中对应的列值不会发生变化)
        2. 当对应的属性不为null时,便会拼接到set片段中,当属性为基本类型时(都有默认值),那么始终都会拼接上去发生修改,那要如何解决这个问题呢?
          • 将基本数据类型改为包装类型
          • 使用update方法,而不是使用updateById方法
      • update:等价于:update employee set xxx = xxx where 其他列= xxx,具体的条件有updateWrapper提供
            //将id为20的员工,名字更新为laoqi
            UpdateWrapper wrapper = new UpdateWrapper<>();
            wrapper.eq("id",20);
            //这种写法的意思是将name值为zs的数据修改器age值为18
            //wrapper.eq("name","zs").set("age",18);
            wrapper.set("name","laoqi");
            employeeMapper.update(null,wrapper);
        
        由上可知update方法,第一个参数(实体类对象)可以为空,条件都写在UpdateWrapper中即可,因为在UpdateWrapper中传入了对应的泛型,且条件和修改都在wrapper.eq和wrapper.set中写了,所以可以这样操作,使用update时,只有将条件设入进来了后才会拼接到对应的set片段中,就不会出现updateById那种自动将基本数据类型设入进set片段的问题了
      • update与updateById如何选用?
        1. 使用updateById场景
          • where条件是id时候update场景
          • 进行全量(所有字段)更新时候
        2. 使用update场景
          • where条件是不确定,或者多条件的update场景
          • 进行部分字段更新时候
      • 删除相关
        1. deleteById根据主键删除:
        2. deleteByMap根据条件删除,多个条件是就只能自动拼接成and了,而不能处理or情况
        3. delete通过QueryWrapper可以处理and,or等各种复杂的情况
        4. deleteBatchIds批量删除
            public void testDelete(){
        
            //1.根据主键删除:DELETE FROM employee WHERE id=?
            //employeeMapper.deleteById(21);
        
            //2.根据条件删除:DELETE FROM employee WHERE name = ?
            // HashMap map = new HashMap<>();
            // map.put("name","zs");
            // employeeMapper.deleteByMap(map);
        
            //3.根据 entity 条件,删除记录,除了更新条件操作使用UpdateWrapper外,
            // 其余的操作都用QueryWrapper
            //DELETE FROM employee WHERE (id = ? AND name = ? OR age = ?)
            QueryWrapper wrapper = new QueryWrapper<>();
            wrapper.eq("id",2L);
            wrapper.eq("name","zs").or().eq("age",18);
            employeeMapper.delete(wrapper);
        
            //4.批量删除:DELETE FROM employee WHERE id IN ( ? , ? , ? )
            //employeeMapper.deleteBatchIds(Arrays.asList(33,44,55));
            }
        
      • select相关:
            @Test
            public void testSelect(){
        
                //1.根据主键查询:
                // 得到语句:SELECT id,name,password,email,age,admin,dept_id
                // FROM employee WHERE id=?
                // employeeMapper.selectById(20L);
        
                //2.根据id批量查询
                //得到语句:SELECT id,name,password,email,age,admin,dept_id
                // FROM employee WHERE id IN ( ? , ? , ? )
                // employeeMapper.selectBatchIds(Arrays.asList(1,2,3));
        
                //3.根据columnMap条件
                //得到语句:SELECT id,name,password,email,age,admin,dept_id
                // FROM employee WHERE name = ? AND age = ?
                // HashMap map = new HashMap<>();
                // map.put("name","zs");
                // map.put("age",18);
                // employeeMapper.selectByMap(map);
        
                //4. 根据 entity 条件,查询一条记录
                //得到的语句:SELECT id,name,password,email,age,admin,dept_id
                // FROM employee WHERE (name = ?)
                //如果查出的结果不止一个就会报错如:
                //Expected one result (or null) to be returned by selectOne(), but found: 20
                // QueryWrapper wrapper = new QueryWrapper<>();
                // wrapper.eq("name","zs");
                // employeeMapper.selectOne(wrapper);
        
                //5.根据 Wrapper 条件,查询总记录数
                //得到语句:SELECT COUNT( 1 ) FROM employee这里的count(1)与count(*)效果一样
                //所以也可以不要wrapper直接写employeeMapper.selectCount(null)就是表示查所有
                // QueryWrapper wrapper = new QueryWrapper<>();
                // employeeMapper.selectCount(wrapper);
        
                //6. 根据 entity 条件,查询全部记录(selectList会把结果封装成实体类对象)
                //得到语句:SELECT id,name,password,email,age,admin,dept_id FROM employee
                //如果wrapper中没有条件,则可以直接写成employeeMapper.selectList(null),查全部
                // QueryWrapper wrapper = new QueryWrapper<>();
                // List selectList = employeeMapper.selectList(wrapper);
                // selectList.forEach(System.out::println);
        
                //7. 根据 Wrapper 条件,查询全部记录(selectMaps会把结果封装成map)
                //得到语句: SELECT id,name,password,email,age,admin,dept_id FROM employee
                //如果查询全部则可以直接写成employeeMapper.selectMaps(null);
                // QueryWrapper wrapper = new QueryWrapper<>();
                // List> maps = employeeMapper.selectMaps(wrapper);
                // maps.forEach(System.out::println);
        
                //8.分页查询(注意:一定要先配置分页插件,这里的分页查询才起效果)
                //得到语句:SELECT id,name,password,email,age,admin,dept_id
                // FROM employee LIMIT ?
                QueryWrapper wrapper = new QueryWrapper<>();
                //参数表示:当前页为1,每页显示4条数据
                Page page = new Page<>(1,4);
                //wrapper是分页的条件
                //这里采用的是链式编程,查询完后的结果还是封装在传入的那个page中的
                employeeMapper.selectPage(page,wrapper);
                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());
            }
        
    5. 构造器相关:
      • 条件构造器:根据名字可以简单理解为条件拼接对象,用于生成 sql 的 where 条件
        1. Wrapper: 条件构造抽象类,最顶端父类
        2. AbstractWrapper: 用于查询条件封装,生成 sql 的 where 条件
        3. QueryWrapper: Entity 对象封装操作类,不是用lambda语法
        4. UpdateWrapper: Update 条件封装,用于Entity对象更新操作
        5. AbstractLambdaWrapper: Lambda 语法使用 Wrapper统一处理解析 lambda 获取column。
        6. LambdaQueryWrapper:看名称也能明白就是用于Lambda语法使用的查询Wrapper
        7. LambdaUpdateWrapper: Lambda 更新封装Wrapper
      • 独有的方法(updateWrapper类型与QueryWrappe类型都是继承的AbstractWrapper,那么各自独有的方法是什么呢?)
        1. UpdateWrapper与LambdaUpadateWrapper独有的是:set,setSql
        2. QueryWrapper与LambdaQueryWrapper独有的是:select
            //需求:将id=zs
            @Test
            public void testUpdate2(){
                UpdateWrapper wrapper = new UpdateWrapper<>();
                wrapper.eq("id", 1L);
                wrapper.set("name", "zs");
                //wrapper.setSql("name='zs'");
                employeeMapper.update(null, wrapper);
            }
        
            //set与setSql的区别?
            //set是采用的占位符形式:UPDATE employee SET name=? WHERE (id = ?)
            //setSql是直接把sql写死的:UPDATE employee SET name='zs' WHERE (id = ?)
        
      • LambdaUpdateWrapper更新
            @Test
            public void testUpdate4(){
                //将id=1的用户name改为zs
                //UPDATE employee SET name=? WHERE (id = ?)
                LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>();
                wrapper.eq(Employee::getId, 1L);
                wrapper.set(Employee::getName,"zs");
                employeeMapper.update(null, wrapper);
            }
        
      • 查询
            @Test
            public void testQuery(){
                //1:查询所有员工,返回员工name,age列
                //得到语句:SELECT name,age FROM employee
                QueryWrapper wrapper = new QueryWrapper<>();
                wrapper.select("name,age");
                //这种写法也可以
                // wrapper.select("name","age");
                employeeMapper.selectList(wrapper);
            }
        
            @Test
            public void testQuery1(){
                //1:查询所有员工, 返回员工 以a字母开头的列(注意了是列名为a,不是列值)
                //得到语句:SELECT id,age,admin FROM employee
                QueryWrapper wrapper = new QueryWrapper<>();
                wrapper.select(Employee.class,
                        tableFieldInfo -> tableFieldInfo.getProperty().startsWith("a"));
                employeeMapper.selectList(wrapper);
            }
        
      • 排序:orderByAsc/orderByDesc
            @Test
            public void testQuery5(){
                //1.根据age,id进行升序查询,password降序
                //得到语句:SELECT id,name,password,email,age,admin,dept_id
                // FROM employee ORDER BY age ASC,id ASC,password DESC
                QueryWrapper wrapper = new QueryWrapper<>();
                wrapper.orderByAsc("age", "id");
                //降序
                wrapper.orderByDesc("password");
                employeeMapper.selectList(wrapper);
            }
        
      • 条件运算符
        1. 比较运算符
          • 全等匹配
                @Test
                public void testQuery8(){
                    //1.查询name为zs,age为18的员工
                    QueryWrapper wrapper = new QueryWrapper<>();
                    Map map = new HashMap<>();
                    map.put("name", "zs");
                    map.put("age", 18);
                    map.put("dept_id", null);
                    //allEq(map)方式得到语句:ELECT id,name,password,email,
                    // age,admin,dept_id
                    // FROM employee WHERE (name = ? AND dept_id IS NULL AND age = ?)
                    wrapper.allEq(map);
            
                    //allEq(map,false)方式得到语句:SELECT id,name,password,email,
                    // age,admin,dept_id
                    // FROM employee WHERE (name = ? AND age = ?)
                    //null2IsNull:该参数表示为ture时:map中与key对应的value为null时就会
                    // 调用key is null 的方法,为false时:就直接将value为null的条件剔除了
                    //null2IsNull默认是为true的,所以我们在 wrapper.allEq(map);方法中
                    //看到的语句是带isnull的
                    // wrapper.allEq(map,false);
                    employeeMapper.selectList(wrapper);
                }
            
          • 全等匹配(带条件过滤的)
                @Test
                public void testQuery9() {
                    //需求:查询满足条件员工信息, 注意传入的map条件中, 包含a的列才参与条件查询
                    QueryWrapper wrapper = new QueryWrapper<>();
                    Map map = new HashMap<>();
                    map.put("name", "zs");
                    map.put("age", 18);
                    //这样写得到的语句为:SELECT id,name,password,email,age,admin,dept_id
                    // FROM employee WHERE (age = ?)
                    // wrapper.allEq((k, v) -> k.contains("g"), map);
            
                    //这样写得到的语句为:SELECT id,name,password,email,age,admin,dept_id
                    // FROM employee WHERE (name = ? AND age = ?)
                    wrapper.allEq((k, v) -> k.contains("a"), map);
            
                    //这个表示只有列值为zs的条件才生效,否则被抛弃
                    //wrapper.allEq((k, v) -> v.equals("zs"), map);
            
                    //总结:k.contains("xx")表示列名必须为xx的才参与查询
            
                    //filter : 过滤函数,是否允许字段传入比对条件中
                    //例1: allEq((k,v) -> k.indexOf("a") >= 0,
                    // {id:1,name:"老王",age:null})--->name = '老王' and age is null
                    //例2: allEq((k,v) -> k.indexOf("a") >= 0,
                    // {id:1,name:"老王",age:null}, false)--->name = '老 王'
                    //总结:allEq((k,v) -> k.indexOf("a") >= 0,条件,false)当条件后面设置的是false时
                    //那么条件中value为null的条件就直接丢弃了
                    employeeMapper.selectList(wrapper);
                }
            
          • 其他相关:
            1. eq::单个参数判断是否相等

            2. ne: 不等于

            3. gt : 大于 >

            4. ge:大于等于 >=

            5. lt:小于 <

            6. le:小于等于 <=

            7. between:位于两者之间,比如年龄在18到30之间( between("age", 18, 30)得到语句--->age between 18 and 30)

            8. notBetween:位于两者之外,用法同上(notBetween("age", 18, 30))

            9. isNull/isNotNull:字段(IS NULL)/( IS NOT NULL),比如( isNull("name")得到语句--->name is null),( isNotNull("name")得到语句--->name is not null)

            10. in/notIn:字段IN (value1, value2, ...)/字段NOT IN (value1, value2, ...)比如:wrapper.in("age", 1L, 2L)得到语句-->age in (1,2);同理notIn得到语句age not in (1,2)

            11. inSql/notInSql:字段 IN ( sql语句 )用法同in与notIn只是insql与notInSql没有使用占位符,是写死的sql语句

            12. like/notLike: LIKE '%值%'(NOT LIKE '%值%'),得到的sql语句为: like("name", "王")得到sql--->name like(not like) '%王%'

            13. likeLeft/likeRight:%在左边或者右边,如: likeLeft("name", "王")得到sql--->name like '%王, likeRight("name", "王")得到sql--->name like '王%'

            14. or:是用来拼接or的,主动调用表示下一个条件不是用and连接(默认是用and连接),而是用or连接,举例如下:

                  @Test
                  public void testQuery24(){
                      QueryWrapper wrapper = new QueryWrapper<>();
                      //age为18并且name为zs,或者id我1的用户
                      //得到语句为:SELECT id,name,password,email,age,admin,dept_id 
                      // FROM employee WHERE (age = ? AND name = ? OR id = ?)
                      wrapper.eq("age", 18)
                              .eq("name", "zs")
                              .or()
                              .eq("id", 1L);
                      employeeMapper.selectList(wrapper);
                  }
              
            15. 嵌套OR:要用or(wr-> wr.ge("age",18).le("age",30));

                  @Test
                  public void testQuery25(){
                      //需求:查询name含有zs字样的,或者 年龄在18到30之间的用户
                      //得到语句: SELECT id,name,password,email,age,admin,dept_id 
                      // FROM employee WHERE (name LIKE ? OR (age >= ? AND age <= ?))
                      QueryWrapper wrapper = new QueryWrapper<>();
                  wrapper.like("name","zs").or(wr-> wr.ge("age",18).le("age",30));
                      employeeMapper.selectList(wrapper);
                  }
              
            16. groupBy:分组查询:注音分组查询,查询的只能是分组的条件,这里的就是dept_id,不能是其他列名,否则会报错(MySQL默认显示的是
              分组数据的第一条,其他数据库就会报错),如果硬要用其他列名,那么只能在统计函数中,如count(id):

                  @Test
                  public void testQuery26(){
                      //需求: 以部门id进行分组查询,查每个部门员工个数
                      //得到语句:SELECT dept_id,count(id) count FROM employee GROUP BY dept_id
                      //注音分组查询,查询的只能是分组的条件,这里的就是dept_id,不能是其他列名,否则会报错(MySQL默认显示的是
                      // 分组数据的第一条,其他数据库就会报错),如果硬要用其他列名,那么只能在统计函数中,如count(id)
                      QueryWrapper wrapper = new QueryWrapper<>();
                      wrapper.groupBy("dept_id");
                      wrapper.select("dept_id","count(id) count");
                      employeeMapper.selectList(wrapper);
                  }
              
            17. having:分组完后执行的的条件

                   @Test
                  public void testQuery27() {
                      // 以部门id进行分组查询,查每个部门员工个数, 将大于3人的部门过滤出来
                      QueryWrapper wrapper = new QueryWrapper<>();
                      wrapper.groupBy("dept_id");
                      wrapper.select("dept_id", "count(id) count");
              
                      //得到语句:SELECT dept_id,count(id) count 
                      // FROM employee GROUP BY dept_id HAVING count>2
                      wrapper.having("count>2");
              
                      //得到语句:SELECT dept_id,count(id) count
                      // FROM employee GROUP BY dept_id HAVING count>?
                      // wrapper.having("count>{0}",2);
                      employeeMapper.selectList(wrapper);
                  }
              
            18. 自定义SQL:1.在resouces下面建立相同路径的XxxMapper.xml文件,像以前一样写好对应的mapper接口方法和对应的sql,然后新建好对应的service,此时service只需要在接口那里继承下IService<实体类>,便可获得常用的crud方法的声明,同时在对应的接口实现类再继承下ServiceImpl<实体类Mapper,实体类>,便可获得常用crud的实现方法,如果我们有自己额外的需求,还是像以前一样在接口处声明,实现类对应实现即可,这个有一个注意点,就是我们自己在实体类里面定义的数据库表中没有的属性时,一定要加上@TableField(exist = false),否则否则mybatisPlus帮我们实现的crud方法就会报错

            19. 分页,这里的IPage就等价于之前的PageInfo(分页信息封装对象),里面有各种分页相关信息

                   @Override
                  public Page queryPage(EmployeeQueryObject qo) {
                      Page page = new Page<>(qo.getCurrentPage(),qo.getPageSize());
                      QueryWrapper wrapper = new QueryWrapper<>();
                      // wrapper.like("name",qo.getKeyWord());
                      wrapper.like(StringUtils.hasText(qo.getKeyWord()),"name",qo.getKeyWord());
                      // return super.page(page,wrapper);
                      return super.page(page,wrapper);
                  }
              
                  @Test
                  public void testPage(){
                      EmployeeQueryObject qo = new EmployeeQueryObject();
                      qo.setPageSize(5);
                      qo.setCurrentPage(2);
                      IPage page = employeeService.queryPage(qo);
                      System.out.println("当前页:" + page.getCurrent());
                      System.out.println("总页数:" + page.getPages());
                      System.out.println("每页显示条数:" + page.getSize());
                      System.out.println("总记录数:" + page.getTotal());
                      System.out.println("当前页显示记录:" + page.getRecords());
                  }
              
            20. 写web项目,导入freemarker后一定要做以下配置

                  #暴露session对象的属性
                  spring.freemarker.expose-session-attributes=true
                  #配置为传统模式,空值自动处理(如果不配置,那么页面很多地方显示不出来)
                  spring.freemarker.settings.classic_compatible=true
                  #重新指定模板文件后缀 springboot 2.2.x 后 默认后缀为 .ftlh
                  spring.freemarker.suffix=.ftl
              

你可能感兴趣的:(MyBatisPlus)