一.概述
在日常开发中,重复编写 CRUD 代码是一件耗时且无聊的工作,作为一个有一定开发经验的人员,在熟悉自己项目的架构和编码风格之后,会通过运用工具来提升编码效率,把时间留给更有探索价值的业务或者技术上,拿更多的时间来摸鱼也行~。MyBatis-Plus 提供的代码生成器可以帮我们一键生成 Entity、Mapper、Service 等模块代码,大大提升开发效率。但在实际项目中,我们经常需要根据业务场景调整生成代码的格式和内容。本篇博客详细讲解 MyBatis-Plus 代码生成器的常见配置及其用途,帮助我们更高效地使用代码生成器。
二.快速入门:代码生成器基础用法
上一篇已经介绍了需要重点关注的代码,和以往的文章不一样,没有太多逻辑分析,该框架基本就是纯代码纯技术,期间会扩展一些知识,帮助刚入门的同学增加视野,偶尔穿插一些我自己开发时的思维和难以解决的问题作为记录和分享吧。这个框架其实是使用了很久的,因为既便它只能完成某个模块CRUD的30%的编码工作,我始终没有想着去完善它,因为总是会代入一些想法中:没时间,没必要,复制其他的功能再改改也挺快。就像我写博客一样,更新的少,更新的慢,也是考虑到这个事情到底有没有必要,继续介绍相关的类和API,跟着代码自上而下的看就行。
AutoGenerator
通过设置各种配置对象(如 GlobalConfig
、DataSourceConfig
、PackageConfig
等)来管理生成代码的详细规则。这些配置决定了生成代码的具体结构和内容。
AutoGenerator autoGenerator = new AutoGenerator();
AutoGenerator
是 MyBatis-Plus 代码生成器的核心类,负责 orchestrating(协调)所有代码生成任务。它提供了一种高度可配置的方式,用于根据用户设置的配置项自动生成项目所需的 Entity(实体类)、Mapper、Service、Controller 等代码。
在代码生成器中,AutoGenerator
的作用可以归纳为以下几点:
1.主要配置项说明
- GlobalConfig(全局配置): 定义代码生成器的全局设置,例如作者名、输出路径、是否覆盖文件等。
- DataSourceConfig(数据源配置): 定义数据库连接信息,例如 JDBC URL、用户名和密码等。
- PackageConfig(包配置): 定义生成代码的包结构,例如实体类、Mapper、Service 的包路径。
- StrategyConfig(策略配置): 定义生成代码的表相关策略,例如哪些表需要生成代码,是否使用 Lombok 等。
- TemplateConfig(模板配置): 定义代码生成所用的模板路径,可以自定义代码生成的样式
2. 启动代码生成任务
通过调用 autoGenerator.execute()
方法,AutoGenerator
会将配置项传递给内部的生成逻辑,执行以下任务:
- 扫描数据库表结构: 通过
DataSourceConfig
连接数据库,并根据指定的表名获取表结构信息。 - 解析配置项: 根据
GlobalConfig
、PackageConfig
等配置项,生成符合用户需求的代码文件路径、包名等。 - 调用模板引擎: 使用内置的模板引擎(如 Freemarker 或 Velocity)将表结构数据填充到模板中。
- 生成文件: 将生成的代码输出到指定的目录。
3.GlobalConfig相关的介绍
// 全局配置
GlobalConfig globalConfig = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
//当前项目名
String projectName = "/generator";
globalConfig.setOutputDir(projectPath + projectName+"/src/main/java");
globalConfig.setAuthor("你的名字");
globalConfig.setFileOverride(false);
globalConfig.setOpen(true);
globalConfig.setIdType(IdType.ID_WORKER);
autoGenerator.setGlobalConfig(globalConfig);
System.getProperty("user.dir")
是返回程序运行时所在的目录路径,一般是启动应用程序的目录,如图:
user.dir是一种固定的用法,类似的还有,
String outputDir = System.getProperty("user.home");
System.out.println("System.getProperty(\"user.home\")结果返回当前用户的主目录路径:"+outputDir);
String author = System.getProperty("user.name");
System.out.println("System.getProperty(\"user.name\")结果返回当前用户的用户名:"+author);
对应的结果截图:
分享一个它可能的作用场景,如果做过物联网相关的项目,需要加载某些设备上的驱动程序,大概率会用到这样的代码来做调试。另外,我之前使用的webDriver,就是用它来加载我需要的浏览器驱动,然后模拟人类点击网页上的某个按钮来下载文件。感兴趣的朋友可以去看看webDriver和类似的工具,动手操作一下,让知识不要孤零零的。
Selenium WebDriver 简介:
Selenium 是最知名的 Web 自动化测试工具,用于自动化测试浏览器操作。WebDriver 是 Selenium 的核心组件,提供了对各种浏览器的支持。
官方地址:WebDriver | Selenium
继续我们的主线任务:
globalConfig.setOpen(true);
全局策略的一部分,代码生成器生成完成之后,是否自动打开文件夹,true是打开。
globalConfig.setFileOverride(false);
setFileOverride(false)
: 表示在代码生成时,不覆盖已存在的文件。如果在生成过程中,目标目录中已经存在同名文件,代码生成器会跳过这些文件,而不会进行覆盖,为true则相反。
globalConfig.setIdType(IdType.ID_WORKER);
最后生成的实体类中,会这样标注id属性:
setIdType作为全局策略的一部分,代码生成器生成的实体类中ID主键是采用自增还是其他,这篇文章是雪花算法ID,主键ID会根据当前机器码,时间,毫秒等来生成,适合分布式项目主键生成策略,几乎不会产生重复,也有重复,但冲突的概率非常小了,框架提供了6种主键生成方式,下面是他们的简单介绍以及比较,如图:
AUTO | 数据库自增 | 数值型 | 简单系统或单机应用 | 简单直接,依赖数据库管理 |
NONE | 无策略 | 任意类型 | 手动管理主键 | 灵活性高 |
INPUT | 手动设置 | 任意类型 | 主键由外部逻辑生成 | 完全控制权 |
ID_WORKER | 雪花算法生成 | 数值型 | 分布式系统或大规模应用 | 高性能、全局唯一 |
UUID | UUID 生成 | 字符串型 | 需要字符串主键 | 唯一性强,易用性高 |
ID_WORKER_STR | 雪花算法生成(字符串) | 字符串型 | 分布式系统 | 唯一性强,兼容字符串主键字段 |
4.DataSourceConfig相关介绍
// 数据源配置 需配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig
.setUrl("jdbc:mysql://127.0.0.1:3309/db_name?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&userAffectedRows=true");
// dataSourceConfig.setSchemaName("public");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("ll775522");
autoGenerator.setDataSource(dataSourceConfig);
这个是较为简单的配置了,配置账户名,密码,驱动,IP,端口,底层原理不分析,会简单使用就OK,额外说说两处
com.mysql.cj.jdbc.Driver
是 MySQL 连接器(Connector/J)在 8.x 版本中使用的新驱动类,取代了旧版本中的 com.mysql.jdbc.Driver,新版本用这个驱动就对了
另外,协议中的回环地址IP并不表示我本地安装了Mysql,这个是做了SSH隧道配置,连接的是远程服务器上的Mysql,端口做了映射,简单了解一下即可,我之所以这么用,就是因为本地连接远程的Mysql超过几分钟不使用就需要重新建立连接,每次打开Mysql查表都要先进行握手,然后第二次点击表格,才能查数据,所以这样做了,CMD控制台可以直接输入这段指令,然后设置一下客户端心跳包间隔时间,复制公钥到远程服务器,避免每次都要密码登录,具体的操作我没有截图,因为步骤已经给出来了,并没有什么难度的操作。下面是截图:
ssh -N -L 3309:localhost:3306 -L 6379:127.0.0.1:6379 登陆名@云服务器ip
5.PackageInfo相关的介绍
// 包配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.smt");
String userOrAdmin = jedis.get("userOrAdmin");
packageConfig.setController("controller."+userOrAdmin);
String moudle = jedis.get("moudle");
packageConfig.setModuleName(moudle);
autoGenerator.setPackageInfo(packageConfig);
可以先忽略jedis的代码,这个是为了解决一些不好处理的代码才加上去的,由于开发的时间过去很久了,已经忘了具体是什么问题。
只需要知道这个配置是用来设置包名的就行,就是每个java文件第一行代码:
package xx.xx.xx.xx.xxx
setParent(XX)和你们项目POM文件中的<groupId></groupId>保持一致就行,规范的前提下这么做没问题。
模块名这里接收一个字符串,为了避免每次启动后发现自己忘记修改模块名的问题,我直接改成了从控制台读取手动输入的字符串,代码如下:
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
6.StrategyConfig相关介绍
// 策略配置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setTablePrefix(packageConfig.getModuleName() + "_");
strategyConfig.setNaming(NamingStrategy.underline_to_camel);//表名映射到实体策略,带下划线的转成驼峰
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);//列名映射到类型属性策略,带下划线的转成驼峰
// strategyConfig.setSuperEntityClass("com.baomidou.ant.common.BaseEntity");
strategyConfig.setEntityLombokModel(true);//实体类使用lombok
TableFill ct = new TableFill("create_time", FieldFill.INSERT);
TableFill ut = new TableFill("update_time", FieldFill.INSERT_UPDATE);
TableFill cu = new TableFill("create_user_id", FieldFill.INSERT);
TableFill uu = new TableFill("update_user_id", FieldFill.INSERT_UPDATE);
final List<TableFill> tlist = new ArrayList<TableFill>();
tlist.add(cu);
tlist.add(uu);
tlist.add(ct);
tlist.add(ut);
strategyConfig.setTableFillList(tlist);
strategyConfig.setEntityBooleanColumnRemoveIsPrefix(false);// Boolean类型字段是否移除is前缀处理
String tableNames = jedis.get("tableNames");
/*ObjectMapper objectMapper = new ObjectMapper();
String [] strings = objectMapper.readValue(tableNames,String[].class);*/
strategyConfig.setInclude(tableNames.split(",")); // 需要生成的表名
autoGenerator.setStrategy(strategyConfig);
strategyConfig.setTablePrefix(packageConfig.getModuleName() + "_");
这个配置主要是完成了表名到实体类的映射,如果你的表名叫t_user,那么生成的实体类叫做User.java,而不是TUser.java,会去掉第一个驼峰。
第二第三行代码直接是框架提供的,这种命名策略直接用框架提供的即可,代码段已含有注释,如果你的项目需要大写开头,可以这么写个配置类,在序列化阶段去转换首字母大写:
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategies.UpperCamelCaseStrategy());
return objectMapper;
}
TableFill ct = new TableFill("create_time", FieldFill.INSERT);
TableFill ut = new TableFill("update_time", FieldFill.INSERT_UPDATE);
TableFill cu = new TableFill("create_user_id", FieldFill.INSERT);
TableFill uu = new TableFill("update_user_id", FieldFill.INSERT_UPDATE);
final List<TableFill> tlist = new ArrayList<TableFill>();
tlist.add(cu);
tlist.add(uu);
tlist.add(ct);
tlist.add(ut);
strategyConfig.setTableFillList(tlist);
上述操作做了一件事情,就是让生成的实体类加上某些注解,截图如下
@TableField注解的fill属性是填充的意思,MybatisPlus的MetaObjectHandler接口提供了新增时,修改时的填充策略,只需要实现接口,重写insertFill(),updateFill()方法,即可自动完成填充,每个公司的业务不一样,网上也有很多例子,我就不复制粘贴了。自动填充,意味着你不需要在业务方法中,额外的编写这样一段代码来更新时间或者用户信息相关的操作。
String tableNames = jedis.get("tableNames");
/*ObjectMapper objectMapper = new ObjectMapper();
String [] strings = objectMapper.readValue(tableNames,String[].class);*/
strategyConfig.setInclude(tableNames.split(",")); // 需要生成的表名
autoGenerator.setStrategy(strategyConfig);
setInclude方法的参数是一个数组,用于生成对应的表,可以是单个,也可以是数组。我这里也是将硬编码部分挪到了控制台输入。直白点说,表名如果只有一个,那么就生成指定表名的增删改查相关的文件,如果是多个,就给参数放一个字符串数组,生成多个表相关的增删改查相关文件。
strategyConfig.setEntityBooleanColumnRemoveIsPrefix(false);// Boolean类型字段是否移除is前缀处理
前篇文章已经介绍,为什么会有这个策略。
7.TemplateConfig相关介绍
// 模板配置:自定义模板路径
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null); // 不生成 XML 文件
autoGenerator.setTemplate(templateConfig);
因为联表查询的操作并不多,且查询条件较为简单,我都是直接用代码来写联表查询,XML配置省略..................
8.InjectionConfig相关介绍
InjectionConfig injectionConfig = new InjectionConfig() {
@Override
public void initMap() {
List<TableInfo> tableInfo = this.getConfig().getTableInfoList();
for (TableInfo tableInfo1 : tableInfo) {
Map<String, Object> map = new HashMap<>();
map.put("idType","ID_WORKER");
map.put("paramType",tableInfo1.getEntityName());
map.put("cnpkg",jedis.get("userOrAdmin"));
map.put("mappingPre",jedis.get("userOrAdmin"));
this.setMap(map);
}
}
};
这个是自定义配置类,是比较重要的,通过它来给模板引擎传递一些额外的信息,或者自定义配置,总而言之,如果需要灵活使用的地方,就通过它来传递,对应的操作及使用方式截图:
上面两张截图是Freemarker Template Language,语法不在本篇介绍,后面看看是否有时间单独一篇。
三.核心类AutoGenerator修改
这个类本来是由框架提供的,可是由于限制了我的发挥,既不能持有它,又不能继承它,也不能重载它的方法,那就只能替换它,为了快速达成目的,我直接将他的源码复制出来了,进行修改,使用到源码文件的地方全局替换成修改过后的。修改的代码截图:
替换的工作量并不大,是很快的,所以这样就完成了一个高度自由,处处可修改的代码生成器。
附上我自己生成的项目结构和实现类的图:
Controller文件
新增用的DTO文件,不含主键ID
分页用的DTO文件
修改时用的DTO文件
接口实现层文件
接口服务层文件
还有Mapper和VO就不一一粘贴了
四.总结
最终通过对上述配置的完善,修改出一个合适自己项目风格的代码,将几个小时的工作量压缩至10s钟。一个属于自己的模板生成器就这么诞生了,几乎没有错误,生成完的代码在经过多次验证之后,如果零错误,可以直接考虑集成到云效或者coding这样的平台中,通过本地的GIT钩子以及他们提供的API编写一个脚本来做持续集成,让增删改查变的有趣~