• SpringBoot集成MybatisPlus项目实操


    前言

    该说的在《SpringBoot集成Mybatis项目实操》一文中都讲了,本文只是将 Mybatis 换成了 Mybatis Plus,带大家将整个项目跑起来。

    本文将实现 SpringBoot+Mybatis Plus 的项目搭建,Mybatis Plus 作为 Mybatis 的增强工具,功能更佳强大,所以需要我们自定义的代码就少了,实现起来也更加简单。

    不说废话了,我们直接进入主题。

    数据库

    本项目采用的是 MySQL 数据库,版本为 8.x,建表语句如下:

    CREATE TABLE `teacher` (
      `id` varchar(36) NOT NULL,
      `name` varchar(20) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      `address` varchar(100) DEFAULT NULL,
      `created_date` timestamp NULL DEFAULT NULL,
      `last_modified_date` timestamp NULL DEFAULT NULL,
      `del_flag` int(2) NOT NULL DEFAULT '0',
      `create_user_code` varchar(36) DEFAULT NULL,
      `create_user_name` varchar(50) DEFAULT NULL,
      `last_modified_code` varchar(36) DEFAULT NULL,
      `last_modified_name` varchar(50) DEFAULT NULL,
      `version` int(11) NOT NULL DEFAULT '1',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_name` (`name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='教师'
    
    CREATE TABLE `student` (
      `id` varchar(36) NOT NULL,
      `name` varchar(20) DEFAULT NULL,
      `teacher_id` varchar(36) NOT NULL,
      `address` varchar(100) DEFAULT NULL,
      `created_date` timestamp NULL DEFAULT NULL,
      `last_modified_date` timestamp NULL DEFAULT NULL,
      `del_flag` int(2) NOT NULL DEFAULT '0',
      `create_user_code` varchar(30) DEFAULT NULL,
      `create_user_name` varchar(50) DEFAULT NULL,
      `last_modified_code` varchar(30) DEFAULT NULL,
      `last_modified_name` varchar(50) DEFAULT NULL,
      `version` int(11) NOT NULL DEFAULT '1',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='学生'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    搭建SpringBoot项目

    使用 IDEA 新建一个 Maven 项目,叫做 mybatisplus-springboot。

    一些共用的基础代码可以参考上篇文章,这里不做重复介绍,会介绍一些 MybatisPlus 相关的代码。

    引入依赖

    <parent>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-parentartifactId>
      <version>2.6.3version>
    parent>
    
    <properties>
      <java.version>1.8java.version>
      <fastjson.version>1.2.73fastjson.version>
      <hutool.version>5.5.1hutool.version>
      <mysql.version>8.0.19mysql.version>
      <mybatis.version>2.1.4mybatis.version>
      <mapper.version>4.1.5mapper.version>
      <org.mapstruct.version>1.4.2.Finalorg.mapstruct.version>
      <org.projectlombok.version>1.18.20org.projectlombok.version>
    properties>
    
    <dependencies>
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
      dependency>
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
      dependency>
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-securityartifactId>
      dependency>
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-validationartifactId>
      dependency>
      <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>${fastjson.version}version>
      dependency>
      <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
        <version>${hutool.version}version>
      dependency>
      <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>${org.projectlombok.version}version>
        <optional>trueoptional>
      dependency>
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
      dependency>
      <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>${mysql.version}version>
        <scope>runtimescope>
      dependency>
      <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.5.1version>
      dependency>
      <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plusartifactId>
        <version>3.5.1version>
      dependency>
      <dependency>
        <groupId>org.springframework.datagroupId>
        <artifactId>spring-data-commonsartifactId>
        <version>2.4.6version>
      dependency>
      <dependency>
        <groupId>org.springdocgroupId>
        <artifactId>springdoc-openapi-uiartifactId>
        <version>1.6.9version>
      dependency>
      <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druid-spring-boot-starterartifactId>
        <version>1.1.18version>
      dependency>
    
      <dependency>
        <groupId>org.mapstructgroupId>
        <artifactId>mapstructartifactId>
        <version>${org.mapstruct.version}version>
      dependency>
      <dependency>
        <groupId>org.mapstructgroupId>
        <artifactId>mapstruct-processorartifactId>
        <version>${org.mapstruct.version}version>
      dependency>
    dependencies>
    
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-maven-pluginartifactId>
        plugin>
      plugins>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    有些依赖不一定是最新版本,而且你看到这篇文章时,可能已经发布了新版本,到时候可以先模仿着将项目跑起来后,再根据自己的需求来升级各项依赖,有问题咱再解决问题。

    分页处理

    MybatisPlus关于分页处理有好几种方式,此处采用的是 IPage 方式。具体操作步骤如下:

    1、定义 Mapper

    @Repository
    public interface TeacherMapper extends BaseMapper<Teacher> {
    
    }
    
    • 1
    • 2
    • 3
    • 4

    2、自定义 IPage 的实现类

    @Data
    public class MbpPage<T> implements IPage<T> {
        private List<T> records;
        private long total;
        private long pageSize;
        private long pageNum;
        private List<OrderItem> orders;
        private boolean optimizeCountSql;
        private boolean isSearchCount;
        private boolean hitCount;
    
        public MbpPage() {
            this.records = Collections.emptyList();
            this.total = 0L;
            this.pageSize = 10L;
            this.pageNum = 1L;
            this.orders = new ArrayList();
            this.optimizeCountSql = true;
            this.isSearchCount = true;
            this.hitCount = false;
        }
    
        public MbpPage(long pageNum, long pageSize) {
            this(pageNum, pageSize, 0L);
        }
    
        public MbpPage(long pageNum, long pageSize, long total) {
            this(pageNum, pageSize, total, true);
        }
    
        public MbpPage(long pageNum, long pageSize, boolean isSearchCount) {
            this(pageNum, pageSize, 0L, isSearchCount);
        }
    
        public MbpPage(long pageNum, long pageSize, long total, boolean isSearchCount) {
            this.records = Collections.emptyList();
            this.total = 0L;
            this.pageSize = 10L;
            this.pageNum = 1L;
            this.orders = new ArrayList();
            this.optimizeCountSql = true;
            this.isSearchCount = true;
            this.hitCount = false;
            if (pageNum > 1L) {
                this.pageNum = pageNum;
            }
    
            this.pageSize = pageSize;
            this.total = total;
            this.isSearchCount = isSearchCount;
        }
    
        public boolean hasPrevious() {
            return this.pageNum > 1L;
        }
    
        public boolean hasNext() {
            return this.pageNum < this.getPages();
        }
    
        @Override
        public List<T> getRecords() {
            return this.records;
        }
    
        @Override
        public MbpPage<T> setRecords(List<T> records) {
            this.records = records;
            return this;
        }
    
        @Override
        public long getTotal() {
            return this.total;
        }
    
        @Override
        public MbpPage<T> setTotal(long total) {
            this.total = total;
            return this;
        }
    
        @Override
        public long getSize() {
            return this.pageSize;
        }
    
        @Override
        public MbpPage<T> setSize(long pageSize) {
            this.pageSize = pageSize;
            return this;
        }
    
        @Override
        public long getCurrent() {
            return this.pageNum;
        }
    
        @Override
        public MbpPage<T> setCurrent(long current) {
            this.pageNum = current;
            return this;
        }
    
        @Override
        public List<OrderItem> orders() {
            return this.getOrders();
        }
    
        public List<OrderItem> getOrders() {
            return this.orders;
        }
    
        public void setOrders(List<OrderItem> orders) {
            this.orders = orders;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    3、请求体继承 MbpPage

    @Getter
    @Setter
    public class TeacherQueryPageDTO extends MbpPage<Teacher> {
    
      private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4、服务层的接口定义分页查询方法,其返回值类型是 IPage<实体类> 。

    // 获取教师分页列表
    	IPage<TeacherVO> queryPage(TeacherQueryPageDTO dto);
    
    • 1
    • 2

    5、服务的实现类要继承 ServiceImpl< Mapper接口类,实体类 > ,重写分页查询方法。

    @Service
    @RequiredArgsConstructor
    public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements
        TeacherService {
    
      private final TeacherStruct teacherStruct;
    
      @Override
      public IPage<TeacherVO> queryPage(TeacherQueryPageDTO dto) {
        IPage<TeacherVO> teacherPage = this.lambdaQuery().page(dto)
            .convert(teacher -> teacherStruct.modelToVO(teacher));
        return teacherPage;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    关于 IPage 的使用,除了上述这种用法,还有别的用法,可以参考这篇文章

    我们得到的分页查询结果是 IPage 对象,可以直接使用,也可以根据需要进行修改,比如下面这个文件:

    @Getter
    @Setter
    public class PageResult<T> {
    
      /**
       * 总条数
       */
      private Long total;
      /**
       * 总页数
       */
      private Integer pageCount;
      /**
       * 每页数量
       */
      private Integer pageSize;
      /**
       * 当前页码
       */
      private Integer pageNum;
    
      /**
       * 分页数据
       */
      private List<T> data;
    
      /**
       * 处理Mybatis Plus分页结果
       */
      public static <T> PageResult<T> ok(IPage<T> iPage) {
        PageResult<T> result = new PageResult<T>();
        result.setTotal(iPage.getTotal());
        int pageCount = (int) Math.ceil((double) result.getTotal() / iPage.getSize());
        result.setPageCount(pageCount);
        result.setPageNum((int) iPage.getCurrent());
        result.setPageSize((int) iPage.getSize());
        result.setData(iPage.getRecords());
        return result;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    MybatisPlus基础实体类

    作为其他实体类的父类,封装了所有的公共字段,包括逻辑删除标志,版本号,创建人和修改人信息。相较于 Mybatis 提供的基础实体类,我们不需要自定义注解,MybatisPlus 提供的注解比较全面。

    @Data
    @Schema(title = "核心基础实体类")
    @NoArgsConstructor
    @AllArgsConstructor
    @SuperBuilder(toBuilder = true)
    public class CoreBase implements Serializable {
    
      @TableId(value = "id", type = IdType.ASSIGN_ID)
      @Schema(name = "")
      private String id;
    
      @Schema(description = "是否删除 0未删除(默认),1已删除")
      @TableField(value = "del_flag")
      @TableLogic(delval = "1", value = "0")
      private Integer delFlag;
    
      @Schema(description = "创建人")
      @TableField(value = "create_user_code", fill = FieldFill.INSERT)
      private String createUserCode;
    
      @Schema(description = "创建人名称")
      @TableField(value = "create_user_name", fill = FieldFill.INSERT)
      private String createUserName;
    
      @Schema(description = "创建时间")
      @TableField(value = "created_date", fill = FieldFill.INSERT)
      @DateTimeFormat(
          pattern = "yyyy-MM-dd HH:mm:ss"
      )
      @JsonFormat(
          pattern = "yyyy-MM-dd HH:mm:ss"
      )
      private LocalDateTime createdDate;
    
      @Schema(description = "修改人代码")
      @TableField(value = "last_modified_code", fill = FieldFill.INSERT_UPDATE)
      private String lastModifiedCode;
    
      @Schema(description = "修改人名称")
      @TableField(value = "last_modified_name", fill = FieldFill.INSERT_UPDATE)
      private String lastModifiedName;
    
      @Schema(description = "修改时间")
      @TableField(value = "last_modified_date", fill = FieldFill.INSERT_UPDATE)
      @DateTimeFormat(
          pattern = "yyyy-MM-dd HH:mm:ss"
      )
      @JsonFormat(
          pattern = "yyyy-MM-dd HH:mm:ss"
      )
      private LocalDateTime lastModifiedDate;
    
      @Schema(description = "版本号")
      @Version
      @TableField(value = "version")
      private String version;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    关于主键生成策略,本项目采用的是 IdType.ASSIGN_ID,即雪花算法,关于其他生成策略,可以参考MybatisPlus 主键策略(type=IdType.ASSIGN_ID等详解) 一文。

    至于实体类中的一些注解,详细介绍推荐阅读MyBatisPlus–注解@TableLogic注解

    MybatisPlus配置类

    MetaObjectHandler是元对象字段填充控制器抽象类,实现公共字段自动写入。

    比如通常,我们在建表时,会设置几个公共字段:创建人(creator)、更新人(uptater)、创建时间(create_time)、更新时间(update_time)。

    每次将实体对象新增入库时,都要设置创建人和创建时间;每次更新实体对象时,都要设置更新人和更新时间;

    MetaObjectHandler 中主要提供了两个方法:

    public interface MetaObjectHandler {
        
        /**
         * 插入元对象字段填充(用于插入时对公共字段的填充)
         *
         * @param metaObject 元对象
         */
        void insertFill(MetaObject metaObject);
    
        /**
         * 更新元对象字段填充(用于更新时对公共字段的填充)
         *
         * @param metaObject 元对象
         */
        void updateFill(MetaObject metaObject);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这些公共字段我们都会封装在一个 Super Entity 类中,所以在这个类中,我们把所有更新和插入时需要做改动的字段都加上 @TableField 注解,并且设置 fill 属性。比如上面我们定义的 CoreBase 类。

    查看我们自定义的实现类:

    @ConditionalOnBean({com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration.class})
    public class MybatisPlusAutoConfiguration {
    
      @Bean
      public MetaObjectHandler metaObjectHandler() {
        return new FillFieldConfiguration();
      }
    
      public static class FillFieldConfiguration implements MetaObjectHandler {
    
        @Override
        public void insertFill(MetaObject metaObject) {
          LocalDateTime now = DateUtil.toLocalDateTime(new Date());
          metaObject.setValue("createUserCode", "1");
          metaObject.setValue("createUserName", "admin");
          metaObject.setValue("createdDate", now);
          metaObject.setValue("lastModifiedCode", "1");
          metaObject.setValue("lastModifiedName", "admin");
          metaObject.setValue("lastModifiedDate", now);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
          DateTime now = DateUtil.date();
          metaObject.setValue("lastModifiedCode", "1");
          metaObject.setValue("lastModifiedName", "admin");
          metaObject.setValue("lastModifiedDate", now);
        }
      }
    
      /**
       * 分页插件
       *
       * @return
       */
      @Bean
      public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    关于上述代码根据项目实际需求进行调整,来填充非关键数据。

    最后在 resources 目录下创建META-INF目录下,在 META-INF 目录下创建 spring.factories 文件,文件内容如下:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.msdn.orm.hresh.common.mybatisPlus.config.MybatisPlusAutoConfiguration
    
    • 1
    • 2

    一键式生成模版代码

    运行 orm-generate 项目,在 swagger 上调用 /build 接口,调用参数如下:

    {
        "database": "mysql_db",
        "flat": true,
        "type": "mybatisplus",
        "group": "hresh",
        "host": "127.0.0.1",
        "module": "orm",
        "password": "root",
        "port": 3306,
        "table": [
            "teacher",
            "student"
        ],
        "username": "root",
        "tableStartIndex":"0"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    代码文件直接移到项目中就行了,稍微修改一下引用就好了。

    功能实现

    请求日志输出

    比如说我们访问 /teachers/queryPage 接口,看看控制台输出情况:

    Request Info      : {"classMethod":"com.msdn.orm.hresh.controller.TeacherController.queryPage","ip":"127.0.0.1","requestParams":{"dto":{"hitCount":false,"optimizeCountSql":true,"records":[{"name":"hresh2","age":46},{"name":"hresh1","age":46},{"name":"hresh0","age":46},{"address":"湖北大学","name":"hresh","age":44},{"address":"武汉大学","name":"acorn2","age":46}],"pageSize":5,"pageNum":1,"total":7,"orders":[{"column":"name","asc":false}],"isSearchCount":true}},"httpMethod":"GET","url":"http://localhost:8802/teachers/queryPage","result":{"code":"200","message":"操作成功","success":true},"methodDesc":"获取教师分页列表","timeCost":93}
    
    • 1

    可以看到,日志输出中包含前端传来的请求体,请求 API,返回结果,API 描述,API 耗时。

    统一返回格式

    比如说分页查询,返回结果如下:

    {
    	"data": {
    		"total": 7,
    		"pageCount": 2,
    		"pageSize": 5,
    		"pageNum": 1,
    		"data": [
    			{
    				"name": "hresh2",
    				"age": 46,
    				"address": null
    			},
    			{
    				"name": "hresh1",
    				"age": 46,
    				"address": null
    			},
    			{
    				"name": "hresh0",
    				"age": 46,
    				"address": null
    			},
    			{
    				"name": "hresh",
    				"age": 44,
    				"address": "湖北大学"
    			},
    			{
    				"name": "acorn2",
    				"age": 46,
    				"address": "武汉大学"
    			}
    		]
    	},
    	"code": "200",
    	"message": "操作成功",
    	"success": true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    如果是新增请求,返回结果为:

    {
    	"data": null,
    	"code": "200",
    	"message": "操作成功",
    	"success": true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此处 data 没有新增的数据,如果项目需要新增的实体信息,可以稍作修改。

    异常处理

    下面简单演示一下参数异常的情况,在 add user 时校验参数值是否为空。

      public Boolean add(TeacherDTO dto) {
        if (StringUtils.isBlank(dto.getName())) {
          BusinessException.validateFailed("name 不能为空");
        }
        return this.save(teacherStruct.dtoToModel(dto));
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果传递的 name 值为空,则返回结果为:

    {
    	"data": null,
    	"code": "400",
    	"message": "name 不能为空",
    	"success": false
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    @Validated分组校验

    修改 TeacherDTO,当新增数据时,校验 name 不为空,修改数据时,address 不为空。

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public class TeacherDTO {
    
      @Schema(name = "")
      @NotBlank(groups = {Add.class})
      private String name;
    
      @Schema(name = "")
      private Integer age;
    
      @Schema(name = "")
      @NotBlank(groups = {Edit.class})
      private String address;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    最后修改 controller 文件

      @PostMapping
      @Operation(description = "新增教师")
      public Result add(@Validated(Add.class) @RequestBody TeacherDTO dto) {
        boolean flag = teacherService.add(dto);
        if (!flag) {
          return Result.failed();
        }
        return Result.ok();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    调用新增接口,故意将 name 置为空,返回结果为:

    {
    	"data": null,
    	"code": "400",
    	"message": "name不能为空",
    	"success": false
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    填充公共字段

    此处依赖于MetaObjectHandler,重点在 MybatisPlusAutoConfiguration 文件。

    public static class FillFieldConfiguration implements MetaObjectHandler {
    
        @Override
        public void insertFill(MetaObject metaObject) {
          LocalDateTime now = DateUtil.toLocalDateTime(new Date());
          metaObject.setValue("createUserCode", "1");
          metaObject.setValue("createUserName", "admin");
          metaObject.setValue("createdDate", now);
          metaObject.setValue("lastModifiedCode", "1");
          metaObject.setValue("lastModifiedName", "admin");
          metaObject.setValue("lastModifiedDate", now);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
          DateTime now = DateUtil.date();
          metaObject.setValue("lastModifiedCode", "1");
          metaObject.setValue("lastModifiedName", "admin");
          metaObject.setValue("lastModifiedDate", now);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上述代码中关于新增者信息和修改者信息,暂时是写死的状态,实际项目中,可以根据 token 信息进行解析,然后来填充新增者和修改者信息。

    批量操作

    这里简单演示一下关于批量新增的代码

      @Override
      public Boolean batchAdd(TeacherDTO dto) {
        List<Teacher> teachers = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
          Teacher teacher = Teacher.builder().name(dto.getName() + i).age(46).address(dto.getAddress())
              .build();
          teachers.add(teacher);
        }
        return this.saveBatch(teachers);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行效果如下:

    SQL查询结果

    分页查询

    前端参数传递:

    {
    	"pageNum": 1,
    	"pageSize": 5,
    	"orders": [
    		{
    			"column": "name",
    			"asc": false
    		}
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    后端代码:

      public IPage<TeacherVO> queryPage(TeacherQueryPageDTO dto) {
        IPage<TeacherVO> teacherPage = this.lambdaQuery().page(dto)
            .convert(teacher -> teacherStruct.modelToVO(teacher));
        return teacherPage;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查询结果为:

    {
    	"data": {
    		"total": 7,
    		"pageCount": 2,
    		"pageSize": 5,
    		"pageNum": 1,
    		"data": [
    			{
    				"name": "hresh2",
    				"age": 46,
    				"address": "华中",
    				"studentVOS": null
    			},
    			{
    				"name": "hresh1",
    				"age": 46,
    				"address": "华师",
    				"studentVOS": null
    			},
    			{
    				"name": "hresh0",
    				"age": 46,
    				"address": "华科",
    				"studentVOS": null
    			},
    			{
    				"name": "hresh",
    				"age": 44,
    				"address": "湖北大学",
    				"studentVOS": null
    			},
    			{
    				"name": "acorn2",
    				"age": 46,
    				"address": "武汉大学",
    				"studentVOS": null
    			}
    		]
    	},
    	"code": "200",
    	"message": "操作成功",
    	"success": true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    动态查询

    MybatisPlus 作为 Mybatis 的升级版,确实有不少优势,比如批量操作、动态查询等。

    查询方法如下:

      public List<TeacherVO> queryList(TeacherDTO dto) {
        List<Teacher> teachers = this.lambdaQuery().like(Teacher::getName, dto.getName())
            .orderByDesc(Teacher::getName).list();
    
        return teacherStruct.modelToVO(teachers);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    执行结果如下:

    {
    	"data": [
    		{
    			"name": "hresh2",
    			"age": 46,
    			"address": null
    		},
    		{
    			"name": "hresh1",
    			"age": 46,
    			"address": null
    		},
    		{
    			"name": "hresh0",
    			"age": 46,
    			"address": null
    		},
    		{
    			"name": "hresh",
    			"age": 44,
    			"address": "湖北大学"
    		}
    	],
    	"code": "200",
    	"message": "操作成功",
    	"success": true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    一对多查询

    比如说我们定义的 User 和 Job 类,存在着一对多的关系,所以查询 User 信息时,还需要返回关联的 Job 数据。

    关于 Mybatis 一对多、多对一处理手段,可以参考我之前的文章

    本项目采用的是结果嵌套处理方式。

    StudentMapper.xml 内容如下:

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.msdn.orm.hresh.mapper.StudentMapper">
    
      <resultMap id="studentResultMap" type="com.msdn.orm.hresh.model.Student">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="teacher_id" property="teacherId"/>
        <result column="address" property="address"/>
        <result column="created_date" property="createdDate"/>
        <result column="last_modified_date" property="lastModifiedDate"/>
        <result column="del_flag" property="delFlag"/>
        <result column="create_user_code" property="createUserCode"/>
        <result column="create_user_name" property="createUserName"/>
        <result column="last_modified_code" property="lastModifiedCode"/>
        <result column="last_modified_name" property="lastModifiedName"/>
        <result column="version" property="version"/>
      resultMap>
    
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    TeacherMapper.xml 文件

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.msdn.orm.hresh.mapper.TeacherMapper">
    
      <resultMap id="teacherResultMap" type="com.msdn.orm.hresh.model.Teacher">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <result column="address" property="address"/>
        <result column="created_date" property="createdDate"/>
        <result column="last_modified_date" property="lastModifiedDate"/>
        <result column="del_flag" property="delFlag"/>
        <result column="create_user_code" property="createUserCode"/>
        <result column="create_user_name" property="createUserName"/>
        <result column="last_modified_code" property="lastModifiedCode"/>
        <result column="last_modified_name" property="lastModifiedName"/>
        <result column="version" property="version"/>
      resultMap>
    
      <resultMap id="teacherResultMap2" type="com.msdn.orm.hresh.model.Teacher"
        extends="com.msdn.orm.hresh.mapper.TeacherMapper.teacherResultMap">
        <collection property="students"
          resultMap="com.msdn.orm.hresh.mapper.StudentMapper.studentResultMap"
          columnPrefix="st_"/>
      resultMap>
    
      <resultMap id="teacherVoResultMap" type="com.msdn.orm.hresh.vo.TeacherVO"
        extends="com.msdn.orm.hresh.mapper.TeacherMapper.teacherResultMap">
        <collection property="studentVOS"
          resultMap="com.msdn.orm.hresh.mapper.StudentMapper.studentResultMap"
          columnPrefix="st_"/>
      resultMap>
    
      <select id="queryList" resultMap="teacherVoResultMap">
        SELECT t.*,
        st.name st_name,
        st.address st_address
        FROM
        teacher t
        LEFT JOIN student st ON t.id = st.teacher_id
        <where>
          <if test="name!=null and name!=''">
            and t.name like concat('%',#{name},'%')
          if>
          <if test="address != null and address !=''">
            and t.address like concat('%',#{address},'%')
          if>
        where>
      select>
    
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    关于上述 xml 配置,如果服务层获取到返回结果后,不需要其他业务操作,可以直接获取 UserVO,反之,我们需要查询得到 User,处理完其他操作后,再转换为 UserVO。userVoResultMap 对应 UserVO 返回结果,userResultMap2 对应 User 结果。我们最终获取的是 UserVO,可以直接返回给前端。

    对应的 UserMapper 文件

    @Repository
    public interface TeacherMapper extends BaseMapper<Teacher> {
    
      List<TeacherVO> queryList(TeacherDTO dto);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们修改 UserService 中的查询方法如下:

    public List<TeacherVO> queryList(TeacherDTO dto) {
      return teacherMapper.queryList(dto);
      //    List teachers = teacherMapper.queryList(dto);
      //    return teacherStruct.modelToVO(teachers);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    同时在 application.yml 文件中打开 SQL 输出配置:

    mybatis-plus:
      mapper-locations: classpath:mapper/*Mapper.xml
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      type-aliases-package: com.msdn.orm.hresh.model
    
    • 1
    • 2
    • 3
    • 4
    • 5

    调用接口,可以发现控制台输出如下:

    ==>  Preparing: SELECT t.*, st.name st_name, st.address st_address FROM teacher t LEFT JOIN student st ON t.id = st.teacher_id WHERE t.name like concat('%',?,'%')
    ==> Parameters: hresh(String)
    <==    Columns: id, name, age, address, created_date, last_modified_date, del_flag, create_user_code, create_user_name, last_modified_code, last_modified_name, version, st_name, st_address
    <==        Row: 1572579347365912577, hresh0, 46, 华科, 2022-09-21 21:31:48, 2022-09-21 21:31:48, 0, 1, admin, 1, admin, 1, 小明, 襄阳四中
    <==        Row: 1572579347365912577, hresh0, 46, 华科, 2022-09-21 21:31:48, 2022-09-21 21:31:48, 0, 1, admin, 1, admin, 1, 小军, 襄阳四中
    <==        Row: 1572579347877617665, hresh1, 46, 华师, 2022-09-21 21:31:48, 2022-09-21 21:31:48, 0, 1, admin, 1, admin, 1, 小红, 襄阳五中
    <==        Row: 1572579347877617665, hresh1, 46, 华师, 2022-09-21 21:31:48, 2022-09-21 21:31:48, 0, 1, admin, 1, admin, 1, 小杰, 襄阳五中
    <==        Row: 1572578485562318850, hresh, 44, 湖北大学, 2022-09-21 21:28:23, 2022-09-21 21:28:23, 0, 1, admin, 1, admin, 1, null, null
    <==        Row: 1572579347881811969, hresh2, 46, 华中, 2022-09-21 21:31:48, 2022-09-21 21:31:48, 0, 1, admin, 1, admin, 1, null, null
    <==      Total: 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    返回结果为:

    {
    	"data": [
    		{
    			"name": "hresh0",
    			"age": 46,
    			"address": "华科",
    			"studentVOS": [
    				{
    					"name": "小明",
    					"teacherId": null,
    					"address": "襄阳四中"
    				},
    				{
    					"name": "小军",
    					"teacherId": null,
    					"address": "襄阳四中"
    				}
    			]
    		},
    		{
    			"name": "hresh1",
    			"age": 46,
    			"address": "华师",
    			"studentVOS": [
    				{
    					"name": "小红",
    					"teacherId": null,
    					"address": "襄阳五中"
    				},
    				{
    					"name": "小杰",
    					"teacherId": null,
    					"address": "襄阳五中"
    				}
    			]
    		},
    		{
    			"name": "hresh",
    			"age": 44,
    			"address": "湖北大学",
    			"studentVOS": []
    		},
    		{
    			"name": "hresh2",
    			"age": 46,
    			"address": "华中",
    			"studentVOS": []
    		}
    	],
    	"code": "200",
    	"message": "操作成功",
    	"success": true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    Swagger

    启动项目后,访问 swagger,页面展示如下:

    swagger

    总结

    不重复总结了,该说的在上篇文章结尾已经说完了,本文就是把 Mybatis 换成 MybatisPlus,这两种框架本来就比较类似,部分代码是可以复用的,所以如果你完全掌握了上篇文章中提及的知识点,学习本篇内容就比较轻松了。

    那么我们就期待下一篇,看看如何玩转 Spring Data JPA 。

    感兴趣的朋友可以去我的 Github 下载相关代码,如果对你有所帮助,不妨 Star 一下,谢谢大家支持!

  • 相关阅读:
    ​JavaScript中的多种进制与进制转换 ​
    MySQL存储过程和函数知识点
    Vue之transition组件
    【Leetcode刷题】复制带随机指针的链表&&有效的括号
    在Broker端进行消息过滤
    【Linux】进程概念与进程状态
    【JS】数据结构之栈
    WPF handyControl 学习样例
    阿里架构师耗时 1 年,把 P8 所需要的整个 Java 体系,都整理到了一起
    微服务架构漫谈,全面汇总微服务架构实战
  • 原文地址:https://blog.csdn.net/Herishwater/article/details/127983149