# iBiz Mybatis-Plus模板

（技术引用链接）

`Mybatis-Plus官方文档`[:arrow_upper_right:](https://mp.baomidou.com/)

`iBizSys模型api文档`[:arrow_upper_right:](https://modelapi.ibizlab.cn/)


## 介绍说明

`Mybatis-Plus`

MyBatis Plus 是国内人员开发的 MyBatis 增强工具，在 MyBatis 的基础上只做增强不做改变，为简化开发、提高效率而生。

`iBizSys`

iBizSys 是以 `Model Driven Development(MDD)`作为核心理论依据的开发框架，iBizSys从`2009年`诞生至今已经经历数个版本，构建出一套完整的系统构建模型，包含: `DevOps模型`、`文档模型`、`流程模型`、`测试模型`等。 iBizSys 提倡以`模型`作为主要内容的高级别抽象的开发方法，并结合市面上较为流行的技术框架形成技术模板，通过`模型`+`模板`的方式快速构建出一套生产级并且具备`复杂性`和`变更能力`的系统。



## 快速开始

## MyBatis-Plus实现CURD  

我们将通过搭建一个简单的Demo来阐述 iBizSys 整合 mybatis-plus 的过程，在此之前，我们假设您已经：

- **熟悉iBizSys实体建立**

- **熟悉MyBatis-Plus**

- **熟悉FreeMaker**
	

## 建立实体

iBizSys将通过管理数据模型的方式来实现对业务表的管理 [查看更多实体建立小知识](https://bbs.ibizlab.cn/)

![produce](img/quickstart/createEntity.png)

## 建立模板

由于MyBatis-Plus 示例中使用Spring Boot作为工程项目，所以本示例中也将使用Spring Boot作为工程项目。

搭建 SpringBoot 项目环境可通过以下两种方式进行：

>在iBizSys模板仓库中fork SpringBoot模板，基于SpringBoot的基础上增加 Mybatis-Plus 的模板配置 `推荐使用`

>自行搭建 SpringBoot 模板环境，并引入 Mybatis-Plus 相关的模板配置

本示例采用fork的方式获取SpringBoot的模板 [查看更多模板仓库的小知识](https://bbs.ibizlab.cn/)

`编写模板方式`

>在线编辑：`iBizSys` 提供模板在线编辑工具，供大家更高效、便捷的编写模板 [查看iBizSys在线编辑工具](https://bbs.ibizlab.cn/)

>离线编辑：由于`iBizSys`的模板是放在`git`仓库中进行托管，用户可在离线环境下编写模板，编辑完成后再将模板上传至`git`中即可。

### 依赖模板：

在 `pom.xml.ftl` 中引入 Mybatis-Plus 所需的相关依赖

```java

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.6</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

```

### 配置模板：

在 `application.yml.ftl`中添加 H2 数据库的相关配置

```java

spring:
  datasource:
    username: test
    password: 'test'
    url: jdbc:h2:mem:demo;
    driver-class-name: org.h2.Driver
    schema: classpath:db/schema-h2.sql
    data: classpath:db/data-h2.sql
    
```


### 启动类模板：

在 启动类` %PUBPRJ%Main.java.ftl `中,添加 `@MapperScan` 注解，扫描 Mapper 文件夹。模板中含 ` % % 、${ } `均为动态参数 [查看模板参数](https://bbs.ibizlab.cn/)

```java

@SpringBootApplication
@MapperScan("${pub.getPKGCodeName()}.*.mapper")
public class ${pub.getCodeName()?lower_case}Main{

	public static void main(String[] args) {
		SpringApplication.run(${pub.getCodeName()?lower_case}Main.class, args);
	}
}


```

### 实体类模板：

实体类模板将循环输出该实体的所有属性，配置 `@TableName`  `@TableId` 注解，用于指定该实体所映射的数据库表及主键属性，以下为调整后的实体类相关的模板代码：

```java

@TableName(value = "${item.getTableName()}")
@Data
public class ${item.codeName}{

<#list item.getPSDEFields() as defield>
<#if defield.isKeyDEField()>
    @TableId(value= "${defield.getName()?lower_case}",type=IdType.UUID)//指定主键生成策略
</#if>
    private ${srfjavatype(defield.stdDataType)} ${defield.codeName?lower_case};
</#list>

}

```

### Mapper类模板：

创建一个Mapper类的模板文件【%DE%Mapper.java.ftl】

```java

public interface ${item.getCodeName()}Mapper extends BaseMapper<${item.getCodeName()}>{


}

```

## 预览成果物

iBizSys提供了即时预览，可以让您在编写模板的同时预览到最终成果物 [查看更多模板编写小技巧](https://bbs.ibizlab.cn/)

>图中左侧为模板的成果物

>图中间为模板代码

>图右侧为动态运行时，可通过动态运行时查看模板所需参数

![produce](img/quickstart/result_preview_de.png)

## 发布模板

您在iBizSys模板工具编写模板后，模板处于暂存状态，若您需要在项目中应用该模板，需要发布该模板。

![produce](img/quickstart/publishFtl.png)

## 发布模型

发布模型的过程即为从模板仓库中获取最新模板合成模型，生产代码的过程。

![produce](img/quickstart/publishSystem.png)

## 查看最终成果物

### 依赖

![produce](img/quickstart/result_pom.png)

### 配置

![produce](img/quickstart/result_yml.png)

### 启动类

![produce](img/quickstart/result_appMain.png)

### 实体类

![produce](img/quickstart/result_de.png)

### Mapper类

![produce](img/quickstart/result_mapper.png)


### 开始使用



添加`DBET`实体建表的schema脚本:

```sql

create table IF NOT EXISTS  T_DBET
(
  dbetid      VARCHAR2(100) not null,
  createman  VARCHAR2(60),
  updateman  VARCHAR2(60),
  createdate DATE,
  dbetname    VARCHAR2(200),
  updatedate DATE
);

```

添加`DBET`实体测试数据的schema脚本


```sql

INSERT INTO T_DBET (dbetid, dbetname) VALUES
(1, '这是第一条实体数据'),
(2, '这是第二条实体数据'),
(3, '这是第三条实体数据'),
(4, '这是第四条实体数据'),
(5, '这是第五条实体数据');

```

添加测试类，进行功能测试，测试类相关代码如下：

```java

@RunWith(SpringRunner.class)
@SpringBootTest(classes = demoMain.class)
public class QuickStartTest {
    @Autowired
    private DBETMapper dbetMapper;

    @Test
    public void testMain() {
        System.out.println(("----- selectAll method test ------"));
        List<DBET> userList =  dbetMapper.selectList(null);
        Assert.assertEquals(5, userList.size());
        userList.forEach(System.out::println);
    }
}

```

控制台输出：

```
----- selectAll method test ------
DBET(dbetname=这是第一条实体数据, createdate=null, createman=null, dbetid=1, updateman=null, updatedate=null)
DBET(dbetname=这是第二条实体数据, createdate=null, createman=null, dbetid=2, updateman=null, updatedate=null)
DBET(dbetname=这是第三条实体数据, createdate=null, createman=null, dbetid=3, updateman=null, updatedate=null)
DBET(dbetname=这是第四条实体数据, createdate=null, createman=null, dbetid=4, updateman=null, updatedate=null)
DBET(dbetname=这是第五条实体数据, createdate=null, createman=null, dbetid=5, updateman=null, updatedate=null)
 
```

## 小结

通过以上几个简单的步骤，您已经成功搭建一个（SpringBoot+MyBatis-Plus）的项目，实现了对数据表的CURD功能。并且您也成功了搭建了一套属于您个人的技术模板，您可以在以后的任何项目当中，使用您搭建的这套技术模板。

iBizSys可以帮助您快速搭建并使用您所搭建的技术模板来生产您的项目，想要了解更多iBizSys模板生产体系？那就继续往下看吧！






## MyBatis-Plus实现关系映射

通过第一章的学习，您已经成功搭建了一个基于（SpringBoot+MyBatis-Plus）的项目，实现了对数据表的CURD功能。但我们日常项目经常使用到关系型数据库，经常会遇到（1:N、N:N）关系场景，本章就向大家介绍iBizSys实体关系。


### 建立实体1:N关系

建立`ONEET`与`MANYET`实体，实体关系为： `ONEET 1：N MANYET`，

实体关系如下图所示：[了解更多实体关系建立小知识](https://bbs.ibizlab.cn/)

![produce](img/relations/der1-n.png)

### 模板支持1:N关系

#### Mapper文件模板

增加Mapper.xml.ftl，用于映射该实体`主从关系`的查询结果集`resultMap`，相关模板代码如下：

```java

<#ibiztemplate>
TARGET=PSDATAENTITY
</#ibiztemplate>
<?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="${pub.getPKGCodeName()}.${item.getPSSystemModule().codeName?lower_case}.mapper.${item.codeName}Mapper">

	<#comment>暴露明细数据查询mapper给主关系调用</#comment>
	<#if item.getMinorPSDERs?? && item.getMinorPSDERs()??>
		<#list item.getMinorPSDERs() as MinorPSDER>
			<#assign MajorEntity = MinorPSDER.getMajorPSDataEntity()>
			<#assign MajorkeyField = MajorEntity.getKeyPSDEField()>
			<#assign MajorField = MinorPSDER.getPSPickupDEField()>
			<#list item.getAllPSDEDataQueries() as singleQuery>
				<#list singleQuery.getAllPSDEDataQueryCodes() as dedqcode>
	<select id="selectBy${MajorField.getName()?lower_case}"  resultMap="${de.codeName}ResultMap">
		<![CDATA[	select m1.* from(	select t1.* from (
		${srfjavasqlcode('${dedqcode.getQueryCode()}')}
		)t1   ) m1  where  ${MajorField.getName()?lower_case}=<#noparse>#{</#noparse>${MajorkeyField.codeName?lower_case}<#noparse>}</#noparse>
		]]>
	</select>
				</#list>
				<#break>
			</#list>
		</#list>
	</#if>


	<#comment>mybatis返回结果映射</#comment>
	<resultMap id="${de.codeName}ResultMap" type="${pub.getPKGCodeName()}.${item.getPSSystemModule().codeName?lower_case}.domain.${item.codeName}" autoMapping="true">
		<id property="${item.getKeyPSDEField().codeName?lower_case}" column="${item.getKeyPSDEField().getName()?lower_case}" /><!--主键字段映射-->
		<#if item.getMinorPSDERs?? && item.getMinorPSDERs()??>
			<#list item.getMinorPSDERs() as MinorPSDER>
				<#assign MajorField = MinorPSDER.getPSPickupDEField()>
		<result property="${MajorField.codeName?lower_case}" column="${MajorField.getName()?lower_case}" /><!--关系字段映射-->
			</#list>
		</#if>
		<#comment>1：N关系中，在子实体中创建父实体的实例对象</#comment>
		<#if item.getMinorPSDERs?? && item.getMinorPSDERs()??>
			<#list item.getMinorPSDERs() as MinorPSDER>
				<#assign MajorEntity = MinorPSDER.getMajorPSDataEntity()>
		<association property="${MinorPSDER.getCodeName()?lower_case}" javaType="${pub.getPKGCodeName()}.${MajorEntity.getPSSystemModule().codeName?lower_case}.domain.${MajorEntity.codeName}" column="${MajorField.getName()?lower_case}" select="${pub.getPKGCodeName()}.${MajorEntity.getPSSystemModule().codeName?lower_case}.mapper.${MajorEntity.codeName}Mapper.selectById" fetchType="lazy"></association>
			</#list>
		</#if>
		<#comment>1：N关系中，在父实体中创建子实体的List集合</#comment>
		<#if item.getMajorPSDERs?? && item.getMajorPSDERs()??>
			<#list item.getMajorPSDERs() as MajorPSDER>
				<#assign MinorEntity = MajorPSDER.getMinorPSDataEntity()>
				<#assign MajorDerField = MajorPSDER.getPSPickupDEField()>
		<collection property="${MajorPSDER.getMinorCodeName()?lower_case}" ofType="${pub.getPKGCodeName()}.${MinorEntity.getPSSystemModule().codeName?lower_case}.domain.${MinorEntity.codeName}" column="${item.getKeyPSDEField().getName()?lower_case}" select="${pub.getPKGCodeName()}.${MinorEntity.getPSSystemModule().codeName?lower_case}.mapper.${MinorEntity.codeName}Mapper.selectBy${MajorDerField.getName()?lower_case}" fetchType="lazy"></collection>
			</#list>
		</#if> 
	</resultMap>

</mapper>

```
#### 实体类模板

在实体类中关联 `resultMap`，并发布出该实体主从关系对象，相关模板代码如下：

```java

@TableName(value = "${item.getTableName()}",resultMap = "${item.codeName}ResultMap")
@Data
public class ${item.codeName}{

<#comment>输出当前实体1:N主实体</#comment>
<#if item.getMinorPSDERs?? && item.getMinorPSDERs()??>
    <#list item.getMinorPSDERs() as MinorPSDER>
        <#assign MajorEntity = MinorPSDER.getMajorPSDataEntity()>
    @TableField(exist = false)//关系主表数据
    private ${MajorEntity.getCodeName()} ${MinorPSDER.getCodeName()?lower_case};
    </#list>
</#if>
<#comment>输出当前实体1:N子实体</#comment>
<#if item.getMajorPSDERs?? && item.getMajorPSDERs()??>
    <#list item.getMajorPSDERs() as MajorPSDER>
        <#assign MinorEntity = MajorPSDER.getMinorPSDataEntity()>
    @TableField(exist = false)//关系子表数据
    private List <${MinorEntity.getCodeName()}> ${MajorPSDER.getMinorCodeName()?lower_case};
    </#list>
</#if>

}

```

## 开始使用

添加主实体`ONEET`、关系实体`MANYET`实体建表的schema脚本

```sql

create table IF NOT EXISTS  T_ONEET
(
  oneetid    VARCHAR2(100) not null,
  oneetname  VARCHAR2(200),
  createman  VARCHAR2(60),
  updateman  VARCHAR2(60),
  createdate DATE,
  updatedate DATE
);
create table IF NOT EXISTS  T_MANYET
(
  manyetid   VARCHAR2(100) not null,
  manyetname VARCHAR2(200),
  oneetid  VARCHAR2(200),
  createman  VARCHAR2(60),
  updateman  VARCHAR2(60),
  createdate DATE,
  updatedate DATE
);


```

添加主实体`ONEET`、关系实体`MANYET`实体测试数据的schema脚本


```sql

INSERT INTO T_ONEET (oneetid, oneetname) VALUES
(1, '主实体1'),
(2, '主实体2');

INSERT INTO T_MANYET (manyetid, manyetname,oneetid) VALUES
(1, '关系实体数据1',1),
(2, '关系实体数据2',1),
(3, '关系实体数据3',2);


```

添加测试类进行功能测试，测试类代码如下：

```java

@RunWith(SpringRunner.class)
@SpringBootTest(classes = demoMain.class)
public class RelationsTest {
    @Autowired
    private ONEETMapper oneetMapper;

    @Test
    public void testMain() {
        System.out.println(("----- selectAll method test ------"));
        List<ONEET> oneets =  oneetMapper.selectList(null);
        for(ONEET oneet: oneets){
            System.out.println(String.format("[%s]关联的关系实体数量为[%s]",oneet.getOneetname(),oneet.getManyetobjs().size()));
        }
    }
}

```

控制台输出：

从测试结果可以看出，在查询主数据时，可以获取到子数据`Manyetobjs`对象，从而实现关联查询。

```
 ----- selectAll method test ------
[主实体1]关联的关系实体数量为[2]
[主实体2]关联的关系实体数量为[1]
 
```

## MyBatis-Plus实现动态查询

通过前两章节的学习，相信你已经初步了解iBizSys的模型发布体系，在第三章，将会向大家介绍iBizSys如何通过MyBatis-Plus实现动态查询。

在项目里可能会经常遇到查询同一个数据表，在不同的地方，需要根据不同的条件进行过滤。此时，查询条件是不固定的，需要动态拼接查询条件进行查询。

技术实现：

可以使用iBizSys为每个实体模型建立搜索项

可以使用MyBatis-Plus提供[条件构造器](https://mp.baomidou.com/guide/wrapper.html)，根据实体搜索项生成条件构造器，在查询时使用条件构造器，MyBatis-Plus将会解析构造器中的条件来动态拼接where条件，从而实现动态查询。

## 配置实体搜索项

建立实体搜索项，支持通过SEX作为条件进行查询[查看更多实体搜索项配置小技巧](https://bbs.ibizlab.cn/)

![produce](img/dycond/search_cond.png)

## 模板支持实体搜索项

### 实体搜索项模板

模板中为每个实体发布实体搜索项 `searchfilter`，搜索条件包含：`模糊匹配`、`等于`、`大于等于`、`小于等于`···，模板代码如下：

```java

@Data
public class ${item.getCodeName()}SearchFilter{

	private QueryWrapper<${item.getCodeName()}> selectCond;
	public ${item.getCodeName()}SearchFilter(){
		this.selectCond=new QueryWrapper<${item.getCodeName()}>();
	}
	/**
	 * 输出实体搜索项
	 */
<#list item.getPSDEFields() as defield>
	<#list defield.getAllPSDEFSearchModes() as formitem>
	private ${srfjavatype(formitem.getPSDEField().stdDataType)} ${formitem.getName()?lower_case};//[${defield.getLogicName()}]
	public void set${formitem.getName()?lower_case?cap_first}(${srfjavatype(formitem.getPSDEField().stdDataType)} ${formitem.getName()?lower_case}) {
        this.${formitem.getName()?lower_case} = ${formitem.getName()?lower_case};
        if(!StringUtils.isEmpty(this.${formitem.getName()?lower_case})){
		<#if formitem.getValueOp() == "LIKE">
            this.selectCond.like("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "LEFTLIKE">
            this.selectCond.likeLeft("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "RIGHTLIKE">
            this.selectCond.likeRight("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "EQ">
            this.selectCond.eq("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "NOTEQ">
            this.selectCond.ne("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "GT">
            this.selectCond.gt("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "GTANDEQ">
            this.selectCond.ge("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "LT">
            this.selectCond.lt("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		<#elseif formitem.getValueOp() == "LTANDEQ">
            this.selectCond.le("${formitem.getPSDEField().getName()?lower_case}", ${formitem.getName()?lower_case});
		</#if>
        }
    }
	</#list>
</#list>

}
```

### 实体服务对象模板

实体服务类对象中，发布`实体数据集`相关模板代码，数据集接收`searchfilter`作为过滤参数，并将`searchfilter`作为参数传递 `mapper` 实现动态查询

```java

@Service
public class ${item.codeName}ServiceImpl extends ServiceImpl<${item.codeName}Mapper, ${item.codeName}> implements ${item.codeName}Service{

    @Resource
    private ${item.codeName}Mapper ${item.codeName?lower_case}Mapper;

<#if item.getAllPSDEDataSets()??>
    <#list item.getAllPSDEDataSets() as dedataset>
	public List<${item.codeName}> list${dedataset.getCodeName()}(${item.codeName}SearchFilter filter) {
		return ${item.codeName?lower_case}Mapper.search${dedataset.getCodeName()}(filter,filter.getSelectCond());
	}
    </#list>
</#if>

 }

```

### Mapper类模板

Mapper类提供接口供service调用，调用mapper.xml

```java

public interface ${item.getCodeName()}Mapper extends BaseMapper<${item.getCodeName()}>{

<#if item.getAllPSDEDataSets()??>
    <#list item.getAllPSDEDataSets() as dedataset>
    List<${item.getCodeName()}> search${dedataset.getCodeName()}(@Param("srf") ${item.getCodeName()}SearchFilter searchfilter,@Param("ew") Wrapper<${item.getCodeName()}> wrapper) ;
    </#list>
</#if>

}


```
### Mapper文件模板

根据 `Mapper类` 中传递过来的搜索条件，动态拼接查询条件

```java

<#comment>实体数据查询</#comment>
<select id="search${singleQuery.getCodeName()}"  resultMap="${de.codeName}ResultMap">
    <![CDATA[select t1.* from (
	${srfjavasqlcode('${dedqcode.getQueryCode()}')}
    )t1]]>
    <where><if test="ew!=null and ew.sqlSegment!=null and !ew.emptyOfWhere"><#noparse>${ew.sqlSegment}</#noparse></if></where>
    <if test="ew!=null and ew.sqlSegment!=null and ew.emptyOfWhere"><#noparse>${ew.sqlSegment}</#noparse></if>
</select>
	 

```

## 开始使用

添加`DBET`实体建表的schema脚本:

```sql

create table IF NOT EXISTS  T_DBET
(
  dbetid     VARCHAR2(100) not null,
  dbetname   VARCHAR2(200),
  sex        VARCHAR2(200),
  createman  VARCHAR2(60),
  updateman  VARCHAR2(60),
  createdate DATE,
  updatedate DATE
);

```

添加`DBET`实体测试数据的schema脚本

```sql

INSERT INTO T_DBET (dbetid, dbetname,sex) VALUES
(1, '这是第一条实体数据','男'),
(2, '这是第二条实体数据','男'),
(3, '这是第三条实体数据','男'),
(4, '这是第四条实体数据','女'),
(5, '这是第五条实体数据','女');

```


添加测试类，测试类相关代码如下：

```java

@RunWith(SpringRunner.class)
@SpringBootTest(classes = demoMain.class)
public class DyCondTest {
    @Autowired
    private DBETService dbetService;

    @Test
    public void testMain() {
        System.out.println(("----- selectAll method test ------"));
        DBETSearchFilter dbetSearchFilter=new DBETSearchFilter();
        dbetSearchFilter.setN_sex_eq("男");
        List<DBET> dbets =  dbetService.listDefault(dbetSearchFilter);
        dbets.forEach(System.out::println);
    }
}

```

控制台输出：

可以看出已经能够根据实体搜索项【searchfilter】中设定的条件，过滤出 SEX=“男” 的数据

```
----- selectAll method test ------
DBET(dbetname=这是第一条实体数据, createdate=null, createman=null, dbetid=1, updateman=null, updatedate=null, sex=男)
DBET(dbetname=这是第二条实体数据, createdate=null, createman=null, dbetid=2, updateman=null, updatedate=null, sex=男)
DBET(dbetname=这是第三条实体数据, createdate=null, createman=null, dbetid=3, updateman=null, updatedate=null, sex=男)
 
```