《从零打造项目》系列文章

东西

  • 比MyBatis Generator更强壮的代码生成器

ORM框架选型

  • SpringBoot项目根底设施建立
  • SpringBoot集成Mybatis项目实操
  • SpringBoot集成Mybatis Plus项目实操
  • SpringBoot集成Spring Data JPA项目实操

数据库改变办理

  • 数据库改变办理:Liquibase or Flyway
  • SpringBoot结合Liquibase完成数据库改变办理

守时使命框架

  • Java守时使命技能分析
  • SpringBoot结合Quartz完成守时使命
  • SpringBoot结合XXL-JOB完成守时使命

缓存

  • Spring Security结合Redis完成缓存功用

安全框架

  • Java应用程序安全框架
  • Spring Security系列文章
  • Spring Security结合JWT完成认证与授权

开发标准

  • 后端必知:遵循Google Java标准并引入checkstyle检查

前语

该说的在《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='学生'

建立SpringBoot项目

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

一些共用的根底代码能够参阅上篇文章,这儿不做重复介绍,会介绍一些 MybatisPlus 相关的代码。

引入依靠

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.6.3</version>
</parent>
<properties>
  <java.version>1.8</java.version>
  <fastjson.version>1.2.73</fastjson.version>
  <hutool.version>5.5.1</hutool.version>
  <mysql.version>8.0.19</mysql.version>
  <mybatis.version>2.1.4</mybatis.version>
  <mapper.version>4.1.5</mapper.version>
  <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
  <org.projectlombok.version>1.18.20</org.projectlombok.version>
</properties>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>${fastjson.version}</version>
  </dependency>
  <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>${hutool.version}</version>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${org.projectlombok.version}</version>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
  </dependency>
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.5.1</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
    <version>2.4.6</version>
  </dependency>
  <dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.6.9</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.18</version>
  </dependency>
  <dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${org.mapstruct.version}</version>
  </dependency>
  <dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${org.mapstruct.version}</version>
  </dependency>
</dependencies>
<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

有些依靠纷歧定是最新版本,并且你看到这篇文章时,可能已经发布了新版本,到时候能够先仿照着将项目跑起来后,再依据自己的需求来晋级各项依靠,有问题咱再解决问题。

分页处理

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

1、界说 Mapper

@Repository
public interface TeacherMapper extends BaseMapper<Teacher> {
}

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;
    }
}

3、恳求体承继 MbpPage

@Getter
@Setter
public class TeacherQueryPageDTO extends MbpPage<Teacher> {
  private String name;
}

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

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

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;
  }
}

关于 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;
  }
}

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;
}

关于主键生成战略,本项目选用的是 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);
}

这些公共字段咱们都会封装在一个 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;
  }
}

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

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

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

一键式生成模版代码

运行 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"
}

代码文件直接移到项目中就行了,略微修正一下引证就好了。

功用完成

恳求日志输出

比方说咱们访问 /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}

能够看到,日志输出中包含前端传来的恳求体,恳求 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
}

假如是新增恳求,回来成果为:

{
	"data": null,
	"code": "200",
	"message": "操作成功",
	"success": true
}

此处 data 没有新增的数据,假如项目需求新增的实体信息,能够稍作修正。

异常处理

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

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

假如传递的 name 值为空,则回来成果为:

{
	"data": null,
	"code": "400",
	"message": "name 不能为空",
	"success": false
}

@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;
}

最终修正 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();
  }

调用新增接口,成心将 name 置为空,回来成果为:

{
	"data": null,
	"code": "400",
	"message": "name不能为空",
	"success": false
}

填充公共字段

此处依靠于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);
    }
  }

上述代码中关于新增者信息和修正者信息,暂时是写死的状况,实践项目中,能够依据 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);
  }

履行作用如下:

SpringBoot集成Mybatis Plus项目实操

分页查询

前端参数传递:

{
	"pageNum": 1,
	"pageSize": 5,
	"orders": [
		{
			"column": "name",
			"asc": false
		}
	]
}

后端代码:

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

查询成果为:

{
	"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
}

动态查询

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);
  }

履行成果如下:

{
	"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
}

一对多查询

比方说咱们界说的 User 和 Job 类,存在着一对多的联系,所以查询 User 信息时,还需求回来相关的 Job 数据。

关于 Mybatis 一对多、多对一处理手段,能够参阅我之前的文章。

本项目选用的是成果嵌套处理方式。

StudentMapper.xml 内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!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>

TeacherMapper.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<!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="teacherResultMap2">
    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>

关于上述 xml 装备,假如服务层获取到回来成果后,不需求其他事务操作,能够直接获取 TeacherVO,但是这就要求咱们界说的 resultMap 是符合 VO 类而非 Model 类,不然那些主键 id、创立时刻等字段就无法处理了。反之,咱们需求查询得到 Teacher,处理完其他操作后,再转换为 TeacherVO。teacherVoResultMap 对应 TeacherVO 回来成果,teacherVoResultMap2 对应 Teacher 成果。咱们最终获取的是 UserVO,能够直接回来给前端。

对应的 UserMapper 文件

@Repository
public interface TeacherMapper extends BaseMapper<Teacher> {
​
 List<Teacher> queryList(TeacherDTO dto);
}

咱们修正 UserService 中的查询方法如下:

public List<TeacherVO> queryList(TeacherDTO dto) {
   List<Teacher> teachers = teacherMapper.queryList(dto);
   return teacherStruct.modelToVO(teachers);
}

此刻 Teacher 实体类界说如下:

public class Teacher extends CoreBase {
​
 private static final long serialVersionUID = 1L;
​
 @TableField("name")
 @Schema(name = "")
 private String name;
​
 @TableField("age")
 @Schema(name = "")
 private Integer age;
​
 @TableField("address")
 @Schema(name = "")
 private String address;
​
 // 数据库表中不存在的字段,需求疏忽掉
 @TableField(exist = false)
 private List<Student> students;
}

同时在 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

调用接口,能够发现控制台输出如下:

==>  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

回来成果为:

{
	"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
}

Swagger

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

SpringBoot集成Mybatis Plus项目实操

总结

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

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

感兴趣的朋友能够去我的 Github 下载相关代码,假如对你有所协助,不妨 Star 一下,谢谢咱们支撑!