【MyBatis-Plus】MyBatis进阶使用
作者:mmseoamin日期:2023-12-18

目录

一、MyBatis-Plus简介

1.1 介绍

1.2 优点

1.3 结构

二、MyBatis-Plus基本使用

2.1 配置

2.2 代码生成

2.3 CRUD接口测试

三、MyBatis-Plus策略详解

3.1 主键生成策略

3.2 雪花ID生成器

3.3 字段自动填充策略

3.4 逻辑删除

四、MyBatis-Plus插件使用

4.1 乐观锁插件

4.1.1 什么是乐观锁和悲观锁?

4.1.2 乐观锁实现

4.2 分页插件 


一、MyBatis-Plus简介

1.1 介绍

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变(也就是说mybatis有的plus都有),为简化开发、提高效率而生。

1.2 优点

  • 代码生成器: MyBatis-Plus 提供了一个代码生成器,可以根据数据库表自动生成实体类、mapper 接口和 XML 映射文件,大大提高了开发效率。
  • CRUD 操作简化: MyBatis-Plus 提供了一系列 CRUD 操作的方法,例如 save()、saveOrUpdate()、updateById() 和 deleteById(),这些方法可以简化常见的数据库操作。
  • 分页查询: MyBatis-Plus 提供了分页查询的支持,您可以使用 PageHelper 插件或者内置的分页功能来实现分页查询。
  • 性能优化: MyBatis-Plus 提供了二级缓存和批量操作的支持,可以提高数据库操作的性能。
  • 代码简洁: MyBatis-Plus 提供了许多有用的注解,可以简化 MyBatis 的代码,例如 @TableId、@TableField 和 @TableLogic。
  • 可扩展性: MyBatis-Plus 提供了许多扩展点,您可以通过自定义插件或拦截器来扩展 MyBatis-Plus 的功能。

    如果您正在使用 MyBatis,我强烈建议您使用 MyBatis-Plus !!! 

    1.3 结构

    【MyBatis-Plus】MyBatis进阶使用,第1张

    1. 核心模块(Core Module):核心模块包含了Mybatis-Plus的核心功能和基本组件。它提供了通用的Mapper接口、通用的CRUD方法的实现、条件构造器、分页插件等核心功能的实现。核心模块是整个框架的基础,其他模块都依赖于它。

    2. 代码生成器(Code Generator):代码生成器是一个可选的辅助工具,用于根据数据库表结构自动生成实体类、Mapper接口、XML映射文件等基础代码。通过代码生成器,开发者可以快速生成基础代码,减少手写重复代码的工作量。

    3. 扩展模块(Extension Module):扩展模块是Mybatis-Plus提供的一些扩展功能和增强组件的集合。它包括了一些常用的增强功能,如逻辑删除、字段自动填充、乐观锁等。开发者可以通过引入扩展模块,轻松地增加这些功能的支持。

    4. 注解(annotation):用于对实体类、Mapper接口以及SQL语句进行配置和标记。通过使用注解,可以简化配置文件的编写,提高代码的可读性和可维护性。  

    简介 | MyBatis-PlusMyBatis-Plus 官方文档【MyBatis-Plus】MyBatis进阶使用,icon-default.png?t=N7T8,第2张https://baomidou.com/pages/24112f/#%E7%89%B9%E6%80%A7

    二、MyBatis-Plus基本使用

    2.1 配置

    1、导入pom依赖

            
            
                com.baomidou
                mybatis-plus-boot-starter
                3.5.2
            
            
            
                org.springframework.boot
                spring-boot-starter-freemarker
            
            
            
                com.baomidou
                mybatis-plus-generator
                3.5.2
            

    2、配置数据库与mybatis-plus

    server:
      port: 8080
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/bookshop
    mybatis-plus:
      # Mybatis Mapper所对应的XML位置
      mapper-locations: classpath:mapper/*.xml
      # 别名包扫描路径
      type-aliases-package: com.ycxw.mybatis_test.model
      # 是否开启自动驼峰命名规则(camel case)映射
      configuration:
        map-underscore-to-camel-case: true
    logging:
      level:
        com.ycxw.mybatis_test.mapper: debug

    2.2 代码生成

    1、添加代码生成器

    package com.ycxw.mybatis_test.config;
    import com.baomidou.mybatisplus.generator.FastAutoGenerator;
    import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
    import com.baomidou.mybatisplus.generator.config.OutputFile;
    import com.baomidou.mybatisplus.generator.config.rules.DateType;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    import lombok.extern.slf4j.Slf4j;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    @Slf4j
    public class MySQLGenerator {
        private final static String URL = "jdbc:mysql://localhost:3306/bookshop";
        private final static String USERNAME = "root";
        private final static String PASSWORD = "123456";
        private final static DataSourceConfig.Builder DATA_SOURCE_CONFIG =
                new DataSourceConfig.Builder(URL, USERNAME, PASSWORD);
        public static void main(String[] args) {
            FastAutoGenerator.create(DATA_SOURCE_CONFIG)
                    .globalConfig(
                            (scanner, builder) ->
                                    builder.author("云村小威")
                                            .outputDir(System.getProperty("user.dir") + "\\src\\main\\java")
                                            .commentDate("yyyy-MM-dd")
                                            .dateType(DateType.TIME_PACK)
                                            .disableOpenDir()
                    )
                    .packageConfig((builder) ->
                            builder.parent("com.ycxw.mybatis_test")
                                    .entity("entity")
                                    .service("service")
                                    .serviceImpl("service.impl")
                                    .mapper("mapper")
                                    .xml("mapper.xml")
                                    .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper"))
                    )
                    .injectionConfig((builder) ->
                            builder.beforeOutputFile(
                                    (a, b) -> log.warn("tableInfo: " + a.getEntityName())
                            )
                    )
                    .strategyConfig((scanner, builder) ->
                            builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                                    .addTablePrefix("tb_", "t_", "lay_", "meeting_", "sys_")
                                    .entityBuilder()
                                    .enableChainModel()
                                    .enableLombok()
                                    .enableTableFieldAnnotation()
                                    .controllerBuilder()
                                    .enableRestStyle()
                                    .enableHyphenStyle()
                                    .build()
                    )
                    .templateEngine(new FreemarkerTemplateEngine())
                    .execute();
        }
        protected static List getTables(String tables) {
            return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
        }
    }

    这段 Java 代码是用于自动生成 MyBatis-Plus 代码的。它使用 FastAutoGenerator 来生成实体类、服务类、服务实现类、映射器接口和映射器 XML 文件。

    代码主要做了以下事情:

    1. 定义数据源配置,指定数据库 URL、用户名和密码。
    2. 创建 FastAutoGenerator 实例,并指定数据源配置。
    3. 配置全局配置,包括作者、输出目录、注释日期和日期类型等。
    4. 配置包配置,包括父包名、实体包名、服务包名、服务实现包名、映射器包名和 XML 包名等。
    5. 配置注入配置,在输出文件之前记录表信息。
    6. 配置策略配置,包括要生成的表名、要移除的前缀、实体类配置、控制器类配置等。
    7. 指定模板引擎为 Freemarker。
    8. 执行代码生成。

    当运行此代码时,它将生成 MyBatis-Plus 代码,并将其输出到指定目录。

    3、运行该类输入需要生成的数据表名

    【MyBatis-Plus】MyBatis进阶使用,第3张

    【MyBatis-Plus】MyBatis进阶使用,第4张

    tips:

            在这里可以发现除了entity实体包下的Book类其他的类和接口都是空了,其实它自带了增删改查等方法的,可以直接调用,如果需要更复杂的数据接口可以自行编写。

    【MyBatis-Plus】MyBatis进阶使用,第5张

    最后注意添加mapper注释交给spring管理,并在启动器添加扫描mapper的注释:

    【MyBatis-Plus】MyBatis进阶使用,第6张

    2.3 CRUD接口测试

    package com.ycxw.mybatis_test.controller;
    import com.github.yitter.idgen.YitIdHelper;
    import com.ycxw.mybatis_test.entity.Book;
    import com.ycxw.mybatis_test.service.IBookService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    /**
     * 

    * 书本信息表 前端控制器 *

    * * @author 云村小威 * @since 2023-12-16 */ @RestController @RequestMapping("/book") public class BookController { @Autowired private IBookService bookService; @GetMapping("/list") public Object list() { return bookService.list(); } @PostMapping("/save") public Object add(Book book) { /*设置雪花id*/ book.setId(YitIdHelper.nextId()); return bookService.save(book); } @PatchMapping("/update") public Object edit(Book book) { return bookService.updateById(book); } @DeleteMapping("/{id}") public Object del(@PathVariable Long id) { return bookService.removeById(id); } }

    查询测试: 

    【MyBatis-Plus】MyBatis进阶使用,第7张等等。。。 

    三、MyBatis-Plus策略详解

    3.1 主键生成策略

    @Getter
    @Setter
    @Accessors(chain = true)
    @TableName("t_book")
    public class Book implements Serializable {
        private static final long serialVersionUID = 1L;
        /**
         * 书本编号
         */
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
    • @TableId注解属性

      属性类型必须指定默认值描述
      valueString""主键字段名
      typeEnumIdType.NONE指定主键类型

      IdType属性:

      描述
      AUTO数据库 ID 自增
      NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
      INPUTinsert 前自行 set 主键值
      ASSIGN_ID分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
      ASSIGN_UUID分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)
      ID_WORKER分布式全局唯一 ID 长整型类型(please use ASSIGN_ID)
      UUID32 位 UUID 字符串(please use ASSIGN_UUID)
      ID_WORKER_STR分布式全局唯一 ID 字符串类型(please use ASSIGN_ID)

      3.2 雪花ID生成器

      提示

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

      自定义示例工程:

      • spring-boot 示例 :传送门
      方法主键生成策略主键类型说明
      nextIdASSIGN_ID,ID_WORKERID_WORKER_STRLong,Integer,String支持自动转换为 String 类型,但数值类型不支持自动转换,需精准匹配,例如返回 Long,实体主键就不支持定义为 Integer
      nextUUIDASSIGN_UUID,UUIDString默认不含中划线的 UUID 生成

      示例:

      【MyBatis-Plus】MyBatis进阶使用,第8张

      【MyBatis-Plus】MyBatis进阶使用,第9张

      为什么要用ID生成器?

      UUID 的主要优点是:

      • 它能够生成真正随机的标识符,不需要依赖于数据库或其他外部服务。
      • 它可以跨不同的系统和平台使用,而不需要进行任何修改。

        Snowflake ID 的主要优点是:

        • 它能够生成递增的唯一标识符。
        • 它可以分布式生成,因此可以扩展到大型系统。

                  首先UUID它是随机生成的,生成的id可能跨度较大。在性能方面,Snowflake ID 优于 UUID。Snowflake ID 的生成速度更快,存储空间更小,并且具备递增性。但是,Snowflake ID 需要依赖于数据库或其他外部服务来存储和管理元数据,并且需要进行额外的配置和维护。

          在实际应用中,可以选择使用 UUID 或 Snowflake ID,具体取决于具体的需求和场景。

      3.3 字段自动填充策略

      • @TableField 字段注解
            /**
             * 书本名称
             */
            @TableField(value = "bookname",fill = FieldFill.INSERT)
            private String bookname;
        属性类型必须指定默认值描述
        valueString""数据库字段名
        existbooleantrue是否为数据库表字段
        fillEnumFieldFill.DEFAULT字段自动填充策略

        Fill 属性值

        描述
        DEFAULT默认不处理
        INSERT插入时填充字段
        UPDATE更新时填充字段
        INSERT_UPDATE插入和更新时填充字段

        在这里我们可以设置该字段是否自动填充以及什么时候填充,一般在时间字段设置自动填充方便查询数据的插入和更新时间,当然下面只是一个简单的案例:

        我们还需要编写 自定义实现类 MyMetaObjectHandler

        package com.ycxw.mybatis_test.config;
        import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
        import lombok.extern.slf4j.Slf4j;
        import org.apache.ibatis.reflection.MetaObject;
        import org.springframework.stereotype.Component;
        @Slf4j
        @Component
        public class MyMetaObjectHandler implements MetaObjectHandler {
            @Override
            public void insertFill(MetaObject metaObject) {
                log.info("插入时自动填充 ....");
                this.strictInsertFill(metaObject, "bookname", String.class, "暂无"); // 起始版本 3.3.0(推荐使用)
            }
            @Override
            public void updateFill(MetaObject metaObject) {
                log.info("修改时自动填充 ....");
                this.strictUpdateFill(metaObject, "updateTime", String.class, "未填写修改名称"); // 起始版本 3.3.0(推荐)
            }
        }

        定义了自动填充,我们在新增或修改时就可以不为该字段赋值。

        3.4 逻辑删除

        为什么要使用逻辑删除?

        1. 保留历史记录:通过逻辑删除,可以保留项目的完整历史记录,而无需物理删除数据。这对于调试、审计和数据分析非常有用。
        2. 恢复数据:如果项目数据被意外删除,通过逻辑删除可以轻松恢复。而物理删除的数据则无法恢复。
        3. 性能优化:逻辑删除可以提高项目的性能,因为不需要物理删除数据。这对于大型项目或具有大量数据的项目尤为重要。
        4. 安全性:逻辑删除可以提高项目的安全性,因为不需要物理删除数据,可以防止不必要的数据泄露。
        5. 合规性:一些行业或法规可能要求保留数据一段特定时间。逻辑删除可以帮助项目遵守这些规定,而无需永久存储数据。

                通过mybatis-plus扩展的逻辑删除功能,再调用删除接口时将删除自动转为修改。将数据状态修改为不可查询到(前提是该表含有特定的状态列)

        使用方法:

        1、全局配置

        【MyBatis-Plus】MyBatis进阶使用,第10张

        2、局部配置

        【MyBatis-Plus】MyBatis进阶使用,第11张

        这里不可以配置@TableLogic注解,但必须指定@TableField并设置对应数据库中的字段名。

         

        3、测试

        【MyBatis-Plus】MyBatis进阶使用,第12张

        例如删除第一条数据:

        【MyBatis-Plus】MyBatis进阶使用,第13张

        再次查看该数据表:

        【MyBatis-Plus】MyBatis进阶使用,第14张

        调用查询测试:

        【MyBatis-Plus】MyBatis进阶使用,第15张

        可以发现刚才删除的数据将不能查到,而数据库仍存在。

        四、MyBatis-Plus插件使用

        4.1 乐观锁插件

        4.1.1 什么是乐观锁和悲观锁?

                乐观锁和悲观锁是两种不同的并发控制机制,用于防止多用户同时修改同一份数据时产生数据不一致的问题。

        • 乐观锁假设在并发操作时,数据不会被其他用户修改,因此在执行更新操作前不需要对数据加锁。在提交更新时,会检查数据是否已经被修改。如果数据已经被修改,则会抛出异常,要求用户重新获取数据并重新提交更新。
        • 悲观锁假设在并发操作时,数据可能会被其他用户修改,因此在执行更新操作前需要先对数据加锁。在更新操作执行完成后,锁才会被释放。这样可以确保在数据更新时,不会被其他用户修改。

          乐观锁的优点:

          • 性能好,因为在大多数情况下不需要加锁。
          • 可伸缩性好,因为不需要维护锁状态。
          • 不会出现死锁。

            乐观锁的缺点:

            • 可能出现数据不一致的问题,因为在提交更新时,数据可能已经被其他用户修改。
            • 需要应用程序代码来处理并发冲突。

              悲观锁的优点:

              • 可以保证数据一致性,因为在更新数据时,其他用户不能修改数据。
              • 不需要应用程序代码来处理并发冲突。

                悲观锁的缺点:

                • 性能差,因为在更新数据时需要加锁。
                • 可伸缩性差,因为需要维护锁状态。
                • 可能出现死锁。

          4.1.2 乐观锁实现

          当要更新一条记录的时候,希望这条记录没有被别人更新。

          乐观锁实现方式:

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

             

            所以先给需要加锁的表添加version列,同时在实体类中加上@Version注解

            @Version
            private Integer version;

            【MyBatis-Plus】MyBatis进阶使用,第16张

          1、配置插件

          package com.ycxw.mybatis_test.config;
          import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
          import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          // Spring Boot 方式
          @Configuration
          public class MybatisPlusConfig {
              @Bean
              public MybatisPlusInterceptor mybatisPlusInterceptor() {
                  MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
                  mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
                  return mybatisPlusInterceptor;
              }
          }

          2、编写修改接口测试

              @PutMapping("/edit")
              public Object update(){
                  Book b1 = bookService.getById("16");
                  b1.setPrice(133F);
                  bookService.updateById(b1);
                  return "1";
              }

          调用查询接口可以看到:首先查询该数据,在修改version值并判断该数据的version值是否等于当前的version值。 

          【MyBatis-Plus】MyBatis进阶使用,第17张

          【MyBatis-Plus】MyBatis进阶使用,第18张

                  可以看到version值已变成2了,没修改一次就会加1。这就意味着当我修改改数据时version变为了2,而其他人同时修改该数据时首先查询到的是1,而我已经修改为2,他人再修改时条件已不再成立将无法修改。

          以下案例进行验证:

              @PutMapping("/edit")
              public Object update(){
                  /*模仿两人同时修改不同数据时*/
                  Book b1 = bookService.getById("16");
                  b1.setPrice(133F);
                  Book b2 = bookService.getById("16");
                  b2.setPrice(210F);
                  bookService.updateById(b1);
                  bookService.updateById(b2);
                  return "1";
              }

          【MyBatis-Plus】MyBatis进阶使用,第19张

          【MyBatis-Plus】MyBatis进阶使用,第20张

                  可以看到只有第一条修改成功了,且version变为3(首先查询的值为2,修改后自动变为3)。如果他人同时于此修改时,而version判断不成立则无法修改。 

          4.2 分页插件 

          1、首先添加分页插件配置,与乐观锁配置位置一样,该类可以同时配置多个插件。

          package com.ycxw.mybatis_test.config;
          import com.baomidou.mybatisplus.annotation.DbType;
          import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
          import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
          import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          // Spring Boot 方式
          @Configuration
          public class MybatisPlusConfig {
              @Bean
              public MybatisPlusInterceptor mybatisPlusInterceptor() {
                  MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
                  /*配置乐观锁*/
                  mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
                  /*配置分页*/
                  //interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
                  mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
                  return mybatisPlusInterceptor;
              }
          }

           2、编写查询接口测试

              @GetMapping("/list")
              public Object list() {
                  //简单的分页查询
                  Page page = new Page<>(1,5);
                  Page res = bookService.page(page, wrapper);
                  res.getTotal();//数据总数
                  res.getPages();//数据总页数
                  return res.getRecords();
              }

           【MyBatis-Plus】MyBatis进阶使用,第21张

          以上只是一个简单的查询案例,mp通过page类实现了简单分页模型。需要进行条件查询则要了解查询构造器:

                  QueryWrapper是Mybatis-Plus提供的一个条件构造器,用于快速构建SQL查询语句的条件部分。

                  QueryWrapper的语法类似于Mybatis的XML文件中的where标签,其最终会被转换为SQL语句的条件部分。我们可以通过链式调用的方式,不断添加查询条件,从而构建出复杂的查询条件。

          【MyBatis-Plus】MyBatis进阶使用,第22张

          代码示例:

              @GetMapping("/list")
              public Object list(@RequestParam(value = "bookname", required = false, defaultValue = "")  String bookname) {
                  //查询条件
                  QueryWrapper wrapper = new QueryWrapper<>();
                  /**
                   * 设置查询条件 bookname like %name%
                   * 如:
                   *    warpper.eq("price",100,300) == price 在 100 ~ 300 之间
                   *    ...
                   */
                  wrapper.like("bookname",bookname);
                  //分页条件条件
                  Page page = new Page<>(1,5);
                  Page res = bookService.page(page, wrapper);
                  res.getTotal();//数据总数
                  res.getPages();//数据总页数
                  return res.getRecords();
              }

           可以看到通过like方法指定注入需要查询的列名,自定拼接到sql中【MyBatis-Plus】MyBatis进阶使用,第23张