Spring Boot 整合 Mybatis Plus 二次简化持久层代码

Posted by 暮夏有五 on 2021-01-22
Estimated Reading Time 13 Minutes
Words 2.8k In Total
Viewed Times

相信很多朋友在项目中使用的 ORM 框架都是 MyBatis,如果单用 MyBatis 来操作数据库的话,需要手写很多单表查询的 SQL 实现。这时候我们往往会选择一个增强工具来实现这些单表 CRUD 操作,这里推荐一款好用的工具 MyBatis Plus!

MyBatis Plus 简介


MyBatis Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis Plus 提供了代码生成器,可以一键生成 controller、service、mapper、model、mapper.xml 代码,同时提供了丰富的 CRUD 操作方法,助我们解放双手!

创建项目


首先我们需要在 Spring Boot 项目中集成 MyBatis Plus,之后我们再详细介绍它的使用方法!

引入依赖

pom.xml 中添加相关依赖,主要是 MyBatis Plus、MyBatis Plus Generator 和 Velocity 模板引擎:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<!--Mybatis-Plus 依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!--Mybatis-Plus 代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
</dependency>
<!--Velocity 模板生成引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
</dependencies>

相关配置

在 Spring Boot 配置文件 application.yml 添加如下配置,配置好数据源和 MyBatis Plus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456

mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml #指定mapper.xml路径
global-config:
db-config:
id-type: auto #全局默认主键类型设置为自增
configuration:
auto-mapping-behavior: partial #只对非嵌套的 resultMap 进行自动映射
map-underscore-to-camel-case: true #开启自动驼峰命名规则映射

添加 MyBatis Plus 的 Java 配置,使用 @MapperScan 注解配置好需要扫码的 Mapper 接口路径,MyBatis Plus自带分页功能,需要配置好分页插件 PaginationInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* MyBatis配置类
*/
@Configuration
@MapperScan("com.antonio.mybatis.plus.modules.*.mapper")
public class MyBatisConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}

代码生成器


MyBatis Plus 提供了代码生成器,可以一键生成 controller、service、mapper、model、mapper.xml 代码,非常方便!

首先我们创建代码生成器类 MyBatisPlusGenerator,直接运行其 main 方法即可生成相关代码;

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* MyBatisPlus代码生成器
*/
public class MyBatisPlusGenerator {

public static void main(String[] args) {
String projectPath = System.getProperty("user.dir") + "/antonio-plus";
String moduleName = scanner("模块名");
String[] tableNames = scanner("表名,多个英文逗号分割").split(",");
// 代码生成器
AutoGenerator autoGenerator = new AutoGenerator();
autoGenerator.setGlobalConfig(initGlobalConfig(projectPath));
autoGenerator.setDataSource(initDataSourceConfig());
autoGenerator.setPackageInfo(initPackageConfig(moduleName));
autoGenerator.setCfg(initInjectionConfig(projectPath, moduleName));
autoGenerator.setTemplate(initTemplateConfig());
autoGenerator.setStrategy(initStrategyConfig(tableNames));
autoGenerator.setTemplateEngine(new VelocityTemplateEngine());
autoGenerator.execute();
}

/**
* 读取控制台内容信息
*/
private static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
System.out.println(("请输入" + tip + ":"));
if (scanner.hasNext()) {
String next = scanner.next();
if (StrUtil.isNotEmpty(next)) {
return next;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}

/**
* 初始化全局配置
*/
private static GlobalConfig initGlobalConfig(String projectPath) {
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(projectPath + "/src/main/java");
globalConfig.setAuthor("macro");
globalConfig.setOpen(false);
globalConfig.setSwagger2(true);
globalConfig.setBaseResultMap(true);
globalConfig.setFileOverride(true);
globalConfig.setDateType(DateType.ONLY_DATE);
globalConfig.setEntityName("%s");
globalConfig.setMapperName("%sMapper");
globalConfig.setXmlName("%sMapper");
globalConfig.setServiceName("%sService");
globalConfig.setServiceImplName("%sServiceImpl");
globalConfig.setControllerName("%sController");
return globalConfig;
}

/**
* 初始化数据源配置
*/
private static DataSourceConfig initDataSourceConfig() {
Props props = new Props("generator.properties");
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl(props.getStr("dataSource.url"));
dataSourceConfig.setDriverName(props.getStr("dataSource.driverName"));
dataSourceConfig.setUsername(props.getStr("dataSource.username"));
dataSourceConfig.setPassword(props.getStr("dataSource.password"));
return dataSourceConfig;
}

/**
* 初始化包配置
*/
private static PackageConfig initPackageConfig(String moduleName) {
Props props = new Props("generator.properties");
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName(moduleName);
packageConfig.setParent(props.getStr("package.base"));
packageConfig.setEntity("model");
return packageConfig;
}

/**
* 初始化模板配置
*/
private static TemplateConfig initTemplateConfig() {
TemplateConfig templateConfig = new TemplateConfig();
//可以对controller、service、entity模板进行配置
//mapper.xml模板需单独配置
templateConfig.setXml(null);
return templateConfig;
}

/**
* 初始化策略配置
*/
private static StrategyConfig initStrategyConfig(String[] tableNames) {
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
strategyConfig.setEntityLombokModel(true);
strategyConfig.setRestControllerStyle(true);
//当表名中带*号时可以启用通配符模式
if (tableNames.length == 1 && tableNames[0].contains("*")) {
String[] likeStr = tableNames[0].split("_");
String likePrefix = likeStr[0] + "_";
strategyConfig.setLikeTable(new LikeTable(likePrefix));
} else {
strategyConfig.setInclude(tableNames);
}
return strategyConfig;
}

/**
* 初始化自定义配置
*/
private static InjectionConfig initInjectionConfig(String projectPath, String moduleName) {
// 自定义配置
InjectionConfig injectionConfig = new InjectionConfig() {
@Override
public void initMap() {
// 可用于自定义属性
}
};
// 模板引擎是Velocity
String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + moduleName
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
injectionConfig.setFileOutConfigList(focList);
return injectionConfig;
}

}

然后在 resources 目录下添加配置文件 generator.properties,添加代码生成器的数据源配置及存放业务代码的基础包名称:

1
2
3
4
5
dataSource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
dataSource.driverName=com.mysql.cj.jdbc.Driver
dataSource.username=root
dataSource.password=123456
package.base=com.antonio.mybatis.plus.modules

细心的朋友可以发现 MyBatisPlusGenerator 中很多配置代码都没添加注释,其实 MyBatis Plus 源码中的中文注释非常完善,只需查看源码即可,这里摘抄一段 DataSourceConfig 中的源码:

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
/**
* 数据库配置
*
* @author YangHu, hcl
* @since 2016/8/30
*/
@Data
@Accessors(chain = true)
public class DataSourceConfig {

/**
* 数据库信息查询
*/
private IDbQuery dbQuery;
/**
* 数据库类型
*/
private DbType dbType;
/**
* PostgreSQL schemaName
*/
private String schemaName;
/**
* 类型转换
*/
private ITypeConvert typeConvert;
/**
* 关键字处理器
* @since 3.3.2
*/
private IKeyWordsHandler keyWordsHandler;
/**
* 驱动连接的URL
*/
private String url;
/**
* 驱动名称
*/
private String driverName;
/**
* 数据库连接用户名
*/
private String username;
/**
* 数据库连接密码
*/
private String password;

//省略若干代码......

}

代码生成器支持两种模式,一种生成单表的代码,比如只生成 pms_brand 表代码可以先输入 pms,后输入 pms_brand

img

生成单表代码结构一览:

img

另一种直接生成整个模块的代码,需要带通配符 *,比如生成 ums 模块代码可以先输入 ums,后输入 ums_*

img

生成整个模块代码结构一览:

img

自定义生成模板


MyBatis Plus 使用模板引擎来生成代码,支持 Velocity(默认)、Freemarker、Beetl模板引擎,这里以 Velocity 为了来介绍下如何自定义生成模板。

首先我们可以从 MyBatis Plus Generator 依赖包的源码中找到默认模板,拷贝到项目的 resources/templates 目录下:

img

MyBatisPlusGenerator 类中对 TemplateConfig 进行配置,配置好各个模板的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* MyBatisPlus代码生成器
*/
public class MyBatisPlusGenerator {

/**
* 初始化模板配置
*/
private static TemplateConfig initTemplateConfig() {
TemplateConfig templateConfig = new TemplateConfig();
//可以对controller、service、entity模板进行配置
templateConfig.setEntity("templates/entity.java");
templateConfig.setMapper("templates/mapper.java");
templateConfig.setController("templates/controller.java");
templateConfig.setService("templates/service.java");
templateConfig.setServiceImpl("templates/serviceImpl.java");
//mapper.xml模板需单独配置
templateConfig.setXml(null);
return templateConfig;
}
}

对模板进行定制,在定制过程中我们可以发现很多内置变量,用于输出到模板中去,这里以 service.java.vm 模板为例子,比如packagetable 这些变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package ${package.Service};

import ${package.Entity}.${entity};
import ${superServiceClassPackage};

/**
* <p>
* $!{table.comment} 服务类
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${kotlin})
interface ${table.serviceName} : ${superServiceClass}<${entity}>
#else
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {

}
#end

搞懂这些变量从哪来的,对我们定制模板很有帮助,其实这些变量都来着于 AbstractTemplateEnginegetObjectMap 方法,具体变量作用可以参考源码:

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
/**
* 模板引擎抽象类
*
* @author hubin
* @since 2018-01-10
*/
public abstract class AbstractTemplateEngine {
/**
* 渲染对象 MAP 信息
*
* @param tableInfo 表信息对象
* @return ignore
*/
public Map<String, Object> getObjectMap(TableInfo tableInfo) {
Map<String, Object> objectMap = new HashMap<>(30);
ConfigBuilder config = getConfigBuilder();
if (config.getStrategyConfig().isControllerMappingHyphenStyle()) {
objectMap.put("controllerMappingHyphenStyle", config.getStrategyConfig().isControllerMappingHyphenStyle());
objectMap.put("controllerMappingHyphen", StringUtils.camelToHyphen(tableInfo.getEntityPath()));
}
objectMap.put("restControllerStyle", config.getStrategyConfig().isRestControllerStyle());
objectMap.put("config", config);
objectMap.put("package", config.getPackageInfo());
GlobalConfig globalConfig = config.getGlobalConfig();
objectMap.put("author", globalConfig.getAuthor());
objectMap.put("idType", globalConfig.getIdType() == null ? null : globalConfig.getIdType().toString());
objectMap.put("logicDeleteFieldName", config.getStrategyConfig().getLogicDeleteFieldName());
objectMap.put("versionFieldName", config.getStrategyConfig().getVersionFieldName());
objectMap.put("activeRecord", globalConfig.isActiveRecord());
objectMap.put("kotlin", globalConfig.isKotlin());
objectMap.put("swagger2", globalConfig.isSwagger2());
objectMap.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
objectMap.put("table", tableInfo);
objectMap.put("enableCache", globalConfig.isEnableCache());
objectMap.put("baseResultMap", globalConfig.isBaseResultMap());
objectMap.put("baseColumnList", globalConfig.isBaseColumnList());
objectMap.put("entity", tableInfo.getEntityName());
objectMap.put("entitySerialVersionUID", config.getStrategyConfig().isEntitySerialVersionUID());
objectMap.put("entityColumnConstant", config.getStrategyConfig().isEntityColumnConstant());
objectMap.put("entityBuilderModel", config.getStrategyConfig().isEntityBuilderModel());
objectMap.put("chainModel", config.getStrategyConfig().isChainModel());
objectMap.put("entityLombokModel", config.getStrategyConfig().isEntityLombokModel());
objectMap.put("entityBooleanColumnRemoveIsPrefix", config.getStrategyConfig().isEntityBooleanColumnRemoveIsPrefix());
objectMap.put("superEntityClass", getSuperClassName(config.getSuperEntityClass()));
objectMap.put("superMapperClassPackage", config.getSuperMapperClass());
objectMap.put("superMapperClass", getSuperClassName(config.getSuperMapperClass()));
objectMap.put("superServiceClassPackage", config.getSuperServiceClass());
objectMap.put("superServiceClass", getSuperClassName(config.getSuperServiceClass()));
objectMap.put("superServiceImplClassPackage", config.getSuperServiceImplClass());
objectMap.put("superServiceImplClass", getSuperClassName(config.getSuperServiceImplClass()));
objectMap.put("superControllerClassPackage", verifyClassPacket(config.getSuperControllerClass()));
objectMap.put("superControllerClass", getSuperClassName(config.getSuperControllerClass()));
return Objects.isNull(config.getInjectionConfig()) ? objectMap : config.getInjectionConfig().prepareObjectMap(objectMap);
}
}

CRUD 操作


MyBatis Plus 的强大之处不止在于它的代码生成功能,还在于它提供了丰富的 CRUD 方法,让我们实现单表 CRUD 几乎不用手写 SQL 实现!

我们之前生成的 PmsBrandMapper 接口由于继承了 BaseMapper接口,直接拥有了各种 CRUD 方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* <p>
* 品牌表 Mapper 接口
* </p>
*
* @author macro
* @since 2020-08-20
*/
public interface PmsBrandMapper extends BaseMapper<PmsBrand> {

}

我们来看下 BaseMapper 中的方法,是不是基本可以满足我们的日常所需了:

img

我们之前生成的 PmsBrandService 接口由于继承了 IService 接口,也拥有了各种 CRUD 方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* <p>
* 品牌表 服务类
* </p>
*
* @author macro
* @since 2020-08-20
*/
public interface PmsBrandService extends IService<PmsBrand> {

}

可以看下比 BaseMapper 中的更加丰富。

img

有了这些 IServiceBaseMapper 中提供的这些方法,我们单表查询就几乎不用手写 SQL 实现了,使用 MyBatis Plus 实现以前 PmsBrandController 的方法更轻松了!

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
/**
* <p>
* 品牌表 前端控制器
* </p>
*
* @since 2020-08-20
*/
@Api(tags = "PmsBrandController", description = "商品品牌管理")
@RestController
@RequestMapping("/brand")
public class PmsBrandController {

private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);

@Autowired
private PmsBrandService brandService;

@ApiOperation("获取所有品牌列表")
@RequestMapping(value = "/listAll", method = RequestMethod.GET)
@ResponseBody
public CommonResult<List<PmsBrand>> getBrandList() {
return CommonResult.success(brandService.list());
}

@ApiOperation("添加品牌")
@RequestMapping(value = "/create", method = RequestMethod.POST)
@ResponseBody
public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
CommonResult commonResult;
boolean result = brandService.save(pmsBrand);
if (result) {
commonResult = CommonResult.success(pmsBrand);
LOGGER.debug("createBrand success:{}", pmsBrand);
} else {
commonResult = CommonResult.failed("操作失败");
LOGGER.debug("createBrand failed:{}", pmsBrand);
}
return commonResult;
}

@ApiOperation("更新指定id品牌信息")
@RequestMapping(value = "/update", method = RequestMethod.POST)
@ResponseBody
public CommonResult updateBrand(@RequestBody PmsBrand pmsBrand) {
CommonResult commonResult;
boolean result = brandService.updateById(pmsBrand);
if (result) {
commonResult = CommonResult.success(pmsBrand);
LOGGER.debug("updateBrand success:{}", pmsBrand);
} else {
commonResult = CommonResult.failed("操作失败");
LOGGER.debug("updateBrand failed:{}", pmsBrand);
}
return commonResult;
}

@ApiOperation("删除指定id的品牌")
@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult deleteBrand(@PathVariable("id") Long id) {
boolean result = brandService.removeById(id);
if (result) {
LOGGER.debug("deleteBrand success :id={}", id);
return CommonResult.success(null);
} else {
LOGGER.debug("deleteBrand failed :id={}", id);
return CommonResult.failed("操作失败");
}
}

@ApiOperation("分页查询品牌列表")
@RequestMapping(value = "/list", method = RequestMethod.GET)
@ResponseBody
public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
@ApiParam("页码") Integer pageNum,
@RequestParam(value = "pageSize", defaultValue = "3")
@ApiParam("每页数量") Integer pageSize) {
Page<PmsBrand> page = new Page<>(pageNum, pageSize);
Page<PmsBrand> pageResult = brandService.page(page);
return CommonResult.success(CommonPage.restPage(pageResult));
}

@ApiOperation("获取指定id的品牌详情")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
return CommonResult.success(brandService.getById(id));
}

}

更多干货请移步:https://antoniopeng.com


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !