Mybatis-plus 学习笔记


注:以下写法均是3.0.5版本时写的!与最新版本有多处不同!

一、快速入门

官网地址:https://baomidou.com/

步骤:

  1. 创建数据库 mybatis-plus

  2. 创建 user 表,并插入数据。

    CREATE TABLE user
    (
        id BIGINT(20) NOT NULL COMMENT '主键ID',
        name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
        age INT(11) NULL DEFAULT NULL COMMENT '年龄',
        email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
        PRIMARY KEY (id)
    );
    
    -- 真实开发中,还需要version(乐观锁)、deleted(逻辑删除)、gmt_create、gmt_modified
    
    INSERT INTO user (id, name, age, email) VALUES
    (1, 'Jone', 18, 'test1@baomidou.com'),
    (2, 'Jack', 20, 'test2@baomidou.com'),
    (3, 'Tom', 28, 'test3@baomidou.com'),
    (4, 'Sandy', 21, 'test4@baomidou.com'),
    (5, 'Billie', 24, 'test5@baomidou.com');
    
  3. 初始化项目

    快速构建一个spring boot项目。

  4. 导入相关依赖。

    
    
        com.baomidou
        mybatis-plus-boot-starter
        3.0.5
    
    
    
        mysql
        mysql-connector-java
    
    
        org.projectlombok
        lombok
    
    

    说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus!版本的差异!

  5. 连接数据库

    spring:
      datasource:
        # MySQL8的驱动(和MySQL5不同)。需要增加时区的配置 serverTimezone=GMT%2B8
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis-plus?serverTimezone=GMT%2B8
        username: root
        password: admin
    server:
      port: 8086
    
  6. mybatis-plus的优势。

    使用传统方式:

    • pojo
    • dao
    • service
    • controller

    使用mybatis-plus:

    • pojo

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class User {
          private Long id;
          private String name;
          private Integer age;
          private String email;
      }
      
    • mapper接口

      /**
       * Mapper接口
       *
       * 继承了BaseMapper,所有的方法都来自于自己的父类。我们也可以编写自己的扩展方法。
       */
      public interface UserMapper extends BaseMapper {
      
      }
      
    • 使用(需要在主程序配置@MapperScan来扫描到UserMapper) @MapperScan("com.example.mybatisplus.mapper")

      @SpringBootTest
      class MybatisplusApplicationTests {
          @Autowired(required = false)
          private UserMapper userMapper;
      
          @Test
          public void testSelect(){
              System.out.println("=====查询所有的数据=====");
              //参数是一个wrapper,条件构造器,没有就填null。
              List list = userMapper.selectList(null);
              list.forEach(System.out::println);
          }
      }
      

二、配置日志

用了Mybatis-plus后,这时的sql是不可见的,我们希望知道它是怎么执行的,所以需要打开日志!

# mybatis配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

三、CRUD扩展

插入操作

@Test
public void testInsert(){
    System.out.println("=====insert method test======");
    User user = new User();
    //并没有设置userId。(帮我们自动生成id)
    user.setName("弹头");
    user.setAge(23);
    user.setEmail("2238224084@qq.com");
    int insert = userMapper.insert(user);
    System.out.println(insert); //受影响的行数

    System.out.println(user);   //id会自动回填
}

数据库插入的id的默认值为:全局的唯一id

主键生成策略

雪花算法:

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。几乎可以保证几乎全球唯一!

Mybatis-Plus自 3.3.0 开始,默认使用雪花算法(ASSIGN_ID)+UUID(不含中划线)

主键自增

我们需要配置主键自增:

  1. 实体类字段上 @TableId(type = IdType.AUTO)
  2. 数据库字段一定要自增!(否则会报错!)

其余源码解释

//这是老版本的方式。新版本已经改变了。
public enum IdType {
    AUTO(0),	//数据库id自增
    NONE(1),	//未设置主键
    INPUT(2),	//手动输入
    ID_WORKER(3),	//默认的全局唯一id
    UUID(4),	//全局唯一id uuid
    ID_WORKER_STR(5);	//默认的全局唯一id,字符串方式
}

更新操作

@Test
public void testUpdate() {
    User user = new User();
    user.setId(1482682712933310466L);
    user.setName("王忠舟");
    int update = userMapper.updateById(user);

    System.out.println("受影响的行数:" + update);
}

@Test
public void testUpdate() {
    User user = new User();
    user.setId(1482682712933310466L);
    user.setName("王忠舟");
    user.setAge(18);
    int update = userMapper.updateById(user);

    System.out.println("受影响的行数:" + update);
}

自动填充

创建时间、修改时间!这些操作都是自动化完成的,我们不希望手动更新!

阿里巴巴开发手册:所有的数据表都要配置上gmt_create(创建时间)、gmt_modified(修改时间)!而且都要自动化!

  1. 在表中新增字段 create_timeupdate_time。取消默认值、更新操作。

  2. 把实体类同步,并在字段属性上增加注解。

    //字段添加填充内容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
  3. 需要自定义实现类,处理这个注解。在项目根路径下新建 /handler/MyMetaObjectHandler.java,并继承 MetaObjectHandler 接口。

    @Slf4j
    @Component  //要被识别,必须放到IOC容器中。
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("start insert fill ...");
            //setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)
            this.setFieldValByName("createTime", new Date(), metaObject);
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("start update fill ...");
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
    
  4. 测试插入和更新操作(观察时间即可!)。

乐观锁

在面试过程中,我们经常会被问道乐观锁、悲观锁!这个其实非常简单!

乐观锁:顾名思义,十分乐观,他总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值测试。

悲观锁:顾名思义,十分悲观,他总是认为会出现问题,无论干什么都会上锁!再去操作!

乐观锁实现方式(这里主要讲解乐观锁机制):

  • 取出记录时,获取当前version。
  • 更新时,带上这个version
  • 执行更新时,set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败。(如果修改之前有人已经修改了该条记录,那么 version = 2 ,不满足该条更新语句,所以就无法操作了。)

mybatis-plus使用步骤:

  1. 给数据库中增加version字段。

  2. 实体类中同步相应的字段。

    @Version    //乐观锁Version注解
    private Integer version;
    
  3. 注册组件

    @Configuration
    //将主程序的扫描移动到配置文件中。
    @MapperScan("com.example.mybatisplus.mapper")
    public class MyBatisPlusConfig {
        //注册乐观锁插件
        @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor() {
            return new OptimisticLockerInterceptor();
        }
    }
    
  4. 测试乐观锁成功!先查询,后修改。

    //测试乐观锁成功
    @Test
    public void testOptimisticLocker(){
        //1、查询用户信息
        User user = userMapper.selectById(1482682712933310469L);
        //2、修改用户信息
        user.setAge(18);
        user.setEmail("2238224084@qq.com");
        //3、执行更新操作
        userMapper.updateById(user);
    }
    

  5. 测试乐观锁失败!

    //测试乐观锁失败(多线程下)
    @Test
    public void testOptimisticLocker2(){
        //线程1
        User user1 = userMapper.selectById(1482682712933310469L);
        user1.setAge(21);
        user1.setName("芜湖");
    
        //模拟另一个线程执行了插队操作。
        User user2 = userMapper.selectById(1482682712933310469L);
        user2.setAge(20);
        //线程1已经查询了,但还没来得及更新,被另一个线程查询并抢先更新了。
        userMapper.updateById(user2);
    
        //如果没有乐观锁,就会覆盖插队线程的值。
        userMapper.updateById(user1);
    }
    

查询操作

//id查询
@Test
public void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}

//批量查询
@Test
public void testSelectBatchIds(){
    List list = Arrays.asList(1L, 2L, 3L);
    List users = userMapper.selectBatchIds(list);
    users.forEach(System.out::println);
}

//条件查询使用之一 map
@Test
public void testSelectByMap(){
    HashMap map = new HashMap<>();
    //自定义查询条件
    map.put("name", "王忠舟");
    List users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

分页查询

分页在网站使用的十分之多!

  • 原始的limit进行分页。

  • pageHelper第三方插件。

  • MP其实也内置了分页插件!

如何使用?

  1. 导入分页插件

    // 分页插件(旧版)
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
    
  2. 直接使用Page对象即可!

    @Configuration
    //将主程序的扫描移动到配置文件中。
    @MapperScan("com.example.mybatisplus.mapper")
    public class MyBatisPlusConfig {   
        //测试分页查询
        @Test
        public void testPage(){
            //参数1:当前页;参数2:页面数据条数。可以设置为变量从前端接收。
            Page page = new Page<>(1,5);
            userMapper.selectPage(page, null);
    
            page.getRecords().forEach(System.out::println);
        }
    }
    

    //测试分页查询
    @Test
    public void testPage(){
        //参数1:当前页;参数2:页面数据条数。可以设置为变量从前端接收。
        Page page = new Page<>(2,5);
        userMapper.selectPage(page, null);
    
        page.getRecords().forEach(System.out::println);
    }
    

当前页和页面数据条数可以设置为变量,从前端接收。

删除操作

//删除
@Test
public void testDelete(){
    int delete = userMapper.deleteById(1482682712933310466L);
    System.out.println("受影响的条数:"+delete);
}

//批量删除
@Test
public void testDeleteBatchIds(){
    int batchIds = userMapper.deleteBatchIds(Arrays.asList(1L, 2L));
    System.out.println("受影响的条数:"+batchIds);
}

//通过map 删除
@Test
public void testDeleteByMap(){
    HashMap map = new HashMap<>();
    map.put("name", "弹头蛋头");
    int delete = userMapper.deleteByMap(map);
    System.out.println("受影响的条数:"+delete);
}

逻辑删除

我们在工作中会遇到一些问题:逻辑删除!

物理删除:从数据库中直接删除

逻辑删除:没有真正从数据库中删除,而是通过一个变量来让他失效。deleted = 0 ==> deleted = 1

管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!

步骤:

  1. 在数据表中增加一个 deleted 字段。

  2. 在实体类中同步属性。

    @TableLogic //逻辑删除
    private Integer deleted;
    
  3. config中注册组件。

    @Configuration
    //将主程序的扫描移动到配置文件中。
    @MapperScan("com.example.mybatisplus.mapper")
    public class MyBatisPlusConfig {
        //逻辑删除(旧版)
        @Bean
        public ISqlInjector sqlInjector() {
            return new LogicSqlInjector();
        }
    }
    
  4. 配置文件中配置逻辑删除

    mybatis-plus:
      global-config:
        db-config:
          # 配置逻辑删除
          logic-delete-value: 1
          logic-not-delete-value: 0
    
  5. 测试删除,再执行查询操作。查看查询结果和表中的结果。

    逻辑删除实际上是更新操作,并不是删除操作

以上的所有CRUD操作及其扩展操作,我们都必须精通掌握。会大大提高工作和写项目的效率!

性能分析插件

我们在平时的开发中,会遇到一些慢sql。测试!

MP也提供性能分析插件,如果超过这个时间就停止运行!

使用:

  1. 导入插件

    //SQL执行效率插件
    @Bean
    @Profile({"dev", "test"})   //设置 dev test 环境开启,保证我们的效率
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        //设置sql执行的最大时间,如果超过了则不执行。(ms/毫秒)
        performanceInterceptor.setMaxTime(100);
        //开启sql格式化
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }
    
  2. 在配置文件中配置环境为dev 或者 test 环境。

    spring:
      profiles:
        # 开发环境
        active: dev
    
  3. 测试使用

    @Test
    public void testSelect() {
        System.out.println("=====selectAll method test=====");
        //参数是一个wrapper,条件构造器,没有就填null。
        List list = userMapper.selectList(null);
        list.forEach(System.out::println);
    }
    

    使用性能分析插件,可以帮助我们提高效率!

条件构造器

十分重要:Wrapper

我们写一些复杂的sql就可以使用它来替代!

使用:

  • 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于20的数据,记住查看输出的sql进行分析。

    @Test
    public void Test(){
        //查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于20
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.isNotNull("name").isNotNull("email").ge("age", 20);
        List users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
    

  • 查询名字为“弹头”的数据(使用selectOne),记住查看输出的sql进行分析。

    @Test
    public void testSelectOne(){
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.eq("name", "弹头");
        //只查询一个的话可以用selectOne,若超过一个或为0个的话会报错。
        System.out.println(userMapper.selectOne(wrapper));
    }
    

  • 查询年龄在 20 - 30 岁之间的用户的数量,记住查看输出的sql进行分析。

    //查询年龄在 20 - 30 岁之间的用户的数量
    @Test
    public void testBetweenAnd(){
        QueryWrapper wrapper = new QueryWrapper<>();
        //区间
        wrapper.between("age", 20, 30);
        //查询结果数
        Integer count = userMapper.selectCount(wrapper);
        System.out.println(count);
    }
    

  • 模糊查询,记住查看输出的sql进行分析。

    @Test
    public void testLike(){
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper
            .notLike("email", "qq")
            .likeRight("email", "test4");   //Right和Left表示 "%" 在哪边
        List> mapList = userMapper.selectMaps(wrapper);
        mapList.forEach(System.out::println);
    }
    

  • 使用子查询,记住查看输出的sql进行分析。

    @Test
    public void testSelectObjs(){
        QueryWrapper wrapper = new QueryWrapper<>();
        //id 在子查询中查出来。
        wrapper.inSql("id", "select id from user where id < 3");
        List objects = userMapper.selectObjs(wrapper);
        objects.forEach(System.out::println);
    }
    
    

  • order排序查询,记住查看输出的sql进行分析。

    @Test
    public void testOrder(){
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        List userList = userMapper.selectList(wrapper);
        userList.forEach(System.out::println);
    }
    

  • 四、代码生成器

    dao、pojo、service、controller都自己去编写完成!

    AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

    写一个代码生成器也挺复杂的,我感觉这东西适合大型项目,需要多个包的。就一个包的小型项目就自己手写也挺快的。

    步骤:

    1. 还需要导入相应依赖

      
      
          com.baomidou
          mybatis-plus-generator
          3.0.5
      
      
      
          org.apache.velocity
          velocity-engine-core
          2.3
      
      
    2. 编写代码

      package com.example.mybatisplus;
      
      import com.baomidou.mybatisplus.annotation.DbType;
      import com.baomidou.mybatisplus.annotation.FieldFill;
      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.generator.AutoGenerator;
      import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
      import com.baomidou.mybatisplus.generator.config.GlobalConfig;
      import com.baomidou.mybatisplus.generator.config.PackageConfig;
      import com.baomidou.mybatisplus.generator.config.StrategyConfig;
      import com.baomidou.mybatisplus.generator.config.po.TableFill;
      import com.baomidou.mybatisplus.generator.config.rules.DateType;
      import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
      
      import java.util.ArrayList;
      
      /**
       * 代码自动生成器
       */
      public class CodeGenerator {
          public static void main(String[] args) {
              // 代码生成器
              AutoGenerator mpg = new AutoGenerator();
              //配置策略
      
              //1、全局配置
              GlobalConfig gc = new GlobalConfig();
              String property = System.getProperty("user.dir");   //项目路径
              gc.setOutputDir(property + "/src/main/java");   //新建文件夹的位置
              gc.setAuthor("dan");    //标注作者
              gc.setOpen(false);  //是否新建完成打开资源管理器
              gc.setFileOverride(false);  //是否覆盖
              gc.setIdType(IdType.ID_WORKER); //主键策略
              gc.setDateType(DateType.ONLY_DATE); //日期类型
              gc.setSwagger2(true);   //自动生成swagger
              mpg.setGlobalConfig(gc);
      
              //2、设置数据源(数据库配置)
              DataSourceConfig dsc = new DataSourceConfig();
              dsc.setUrl("jdbc:mysql://localhost:3306/mybatis-plus?serverTimezone=GMT%2B8");
              dsc.setDriverName("com.mysql.cj.jdbc.Driver");
              dsc.setUsername("root");
              dsc.setPassword("admin");
              dsc.setDbType(DbType.MYSQL);
              mpg.setDataSource(dsc);
      
              //3、包的配置
              PackageConfig pc = new PackageConfig();
              pc.setModuleName("blog");
              pc.setParent("com.example");
              pc.setEntity("pojo");
              pc.setMapper("mapper");
              pc.setController("controller");
              pc.setService("service");
              mpg.setPackageInfo(pc);
      
              //4、策略配置
              StrategyConfig sc = new StrategyConfig();
              sc.setInclude("user");  //映射数据库中的表名
              sc.setNaming(NamingStrategy.underline_to_camel);
              sc.setColumnNaming(NamingStrategy.underline_to_camel);
              sc.setEntityLombokModel(true);  //自动Lombok。
              sc.setLogicDeleteFieldName("deleted");  //逻辑删除字段
              TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
              TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
              ArrayList list = new ArrayList<>();
              list.add(createTime);
              list.add(updateTime);
              sc.setTableFillList(list);
              //乐观锁
              sc.setVersionFieldName("version");  //乐观锁字段
              sc.setRestControllerStyle(true);    //驼峰命名
              mpg.setStrategy(sc);
      
              //执行
              mpg.execute();
          }
      }