MybatisPlus最新代碼生成器(3.5.1+)使用教程(1)——輸出路徑詳細解析
MybatisPlus最新代碼生成器(3.5.1+)使用教程(2)——輸出文件名詳細解析
MybatisPlus最新代碼生成器(3.5.1+)使用教程(3)——指定數據庫表詳細解析
MybatisPlus最新代碼生成器(3.5.1+)使用教程(4)——文件模板解析
簡介
MyBatisPlus 基於 MyBatis 進行封裝,強調“為簡化開發而生”,極大提高了基於數據庫的 Web 應用開發的效率。鑒於目前網絡上 mybatis-plus-generator 的進階使用教程寥寥無幾,所以我決定拋磚引玉,為大家愉快地使用代碼生成器掃清障礙。
mybatis-plus-generator 替我們生成的文件類型,主要有六種:
從 代碼生成器(新) 可以看到一個簡單的示例代碼:
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 設置作者
.enableSwagger() // 開啟 swagger 模式
.fileOverride() // 覆蓋已生成文件
.outputDir("D://"); // 指定輸出目錄
})
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 設置父包名
.moduleName("system") // 設置父包模塊名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 設置mapperXml生成路徑
})
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 設置需要生成的表名
.addTablePrefix("t_", "c_"); // 設置過濾表前綴
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默認的是Velocity引擎模板
.execute();
跟蹤 execute 的代碼,可以追蹤到 com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine 的 batchOutput 方法:
public AbstractTemplateEngine batchOutput() {
try {
ConfigBuilder config = this.getConfigBuilder();
List<TableInfo> tableInfoList = config.getTableInfoList();
tableInfoList.forEach(tableInfo -> {
Map<String, Object> objectMap = this.getObjectMap(config, tableInfo);
Optional.ofNullable(config.getInjectionConfig()).ifPresent(t -> {
t.beforeOutputFile(tableInfo, objectMap);
// 輸出自定義文件
outputCustomFile(t.getCustomFile(), tableInfo, objectMap);
});
// Mp.java
outputEntity(tableInfo, objectMap);
// mapper and xml
outputMapper(tableInfo, objectMap);
// service
outputService(tableInfo, objectMap);
// MpController.java
outputController(tableInfo, objectMap);
});
} catch (Exception e) {
throw new RuntimeException("無法創建文件,請檢查配置信息!", e);
}
return this;
}
查看所有的 outputXXX 方法代碼,都有 String xxxPath = getPathInfo(OutputFile.xxx);
,且文件的輸出路徑統一格式為:{xxxPath}/{文件名}.{文件后綴}
,例如實體文件的輸出路徑代碼為:
String entityFile = String.format((entityPath + File.separator + "%s" + suffixJavaOrKt()), entityName);
既然,文件完整路徑的拼接規則 我們已經知悉,我們重新聚焦到 getPathInfo 方法源碼:
以下是 ConfigBuilder 的源碼:
也就是說,輸出文件所在文件夾的路徑 是從 com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder 的成員映射 pathInfo 中獲取的。
PackageConfig.packageInfo≠ConfigBuilder.packageInfo
我們在寫 com.baomidou.mybatisplus.generator.FastAutoGenerator 代碼時,可以用 .packageConfig(builder -> builder.pathInfo(customPathInfo))
設置 PackageConfig.pathInfo,
但是,查看 ConfigBuilder 構造函數源碼:
我們自定義的 PackageConfig.pathInfo 需要經過 PathInfoHandler 處理才能得到最終的 ConfigBuilder.pathInfo !
PathInfoHandler源碼解析
結論1:自定義路徑優先級高於默認輸出路徑
PathInfoHandler(GlobalConfig globalConfig, TemplateConfig templateConfig, PackageConfig packageConfig) {
this.outputDir = globalConfig.getOutputDir();
this.packageConfig = packageConfig;
// 設置默認輸出路徑
this.setDefaultPathInfo(globalConfig, templateConfig);
// 覆蓋自定義路徑
Map<OutputFile, String> pathInfo = packageConfig.getPathInfo();
if (CollectionUtils.isNotEmpty(pathInfo)) {
// 自定義路徑優先級高於默認輸出路徑,如果設置了自定義路徑,將覆蓋默認輸出路徑
this.pathInfo.putAll(pathInfo);
}
}
也正因為如此,所以在設置自定義路徑時,需要把 項目目錄,模塊名稱,src/main/java 或者 src/main/resources,以及包名 全都考慮進去! 例如:
// 自定義路徑
Map<OutputFile, String> customPathInfo = new EnumMap<>(OutputFile.class);
// 項目路徑
String projectPath = System.getProperty("user.dir");
// 子模塊名稱
String webModulePath = "platform-web";
// src/main/java
String srcMain = String.join(File.separator, "src", "main", "java");
// 包名
String packageName = "org.coderead.controller".replace('.', File.separatorChar);
// 自定義控制器路徑
customPathInfo.put(OutputFile.controller, String.join(File.separator, projectPath, webModulePath, srcMain, packageName));
FastAutoGenerator.create("url", "username", "password")
.packageConfig(builder -> buidler.pathInfo(customPathInfo))
.execute();
我把 FastAutoGenerator 的客戶端代碼放在了 code-generator 模塊,給大家展示一下 controller 文件的輸出路徑:
結論2:默認所有文件輸出到同一個模塊中
如果跟蹤 PathInfoHandler 的 setDefaultPathInfo 方法,代碼會來到如下位置:
而 outputDir 來自於 PathInfoHandler 的構造函數:
而這個全局配置則來自於 FastAutoGenerator 客戶端代碼中的全局配置:
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 設置作者
.outputDir("D://"); // 指定輸出目錄
})
.execute();
但是,這還不是完整路徑,繼續往下看 PackageConfig 的 getPackageInfo 的源碼:
關鍵就在於這個私有的無參 getPackageInfo() 方法:
@NotNull
public Map<String, String> getPackageInfo() {
// 這個方法會被執行多次,因此首次進入該方法需要初始化
if (packageInfo.isEmpty()) {
// 指定模塊名稱
packageInfo.put(ConstVal.MODULE_NAME, this.getModuleName()); // 對應下圖中 .moduleName("system")
// 獲取 實體類 的包名
packageInfo.put(ConstVal.ENTITY, this.joinPackage(this.getEntity())); // 對應下圖中 .entity("entity")
// 獲取 Mapper 接口的包名
packageInfo.put(ConstVal.MAPPER, this.joinPackage(this.getMapper())); // 對應下圖中 .mapper("dao")
// 獲取 Mapper.xml 的“包”名
packageInfo.put(ConstVal.XML, this.joinPackage(this.getXml())); // 對應下圖中 .xml("generate")
// 獲取 Service 接口的包名
packageInfo.put(ConstVal.SERVICE, this.joinPackage(this.getService())); // 對應下圖中 .service("service")
// 獲取 ServiceImpl 實現類的包名
packageInfo.put(ConstVal.SERVICE_IMPL, this.joinPackage(this.getServiceImpl())); // 這個稍后再說,因為我想放到 service.impl 包中
// 獲取 Controller 的包名
packageInfo.put(ConstVal.CONTROLLER, this.joinPackage(this.getController())); // 對應下圖中 .controller("controller")
packageInfo.put(ConstVal.OTHER, this.joinPackage(this.getOther()));
packageInfo.put(ConstVal.PARENT, this.getParent()); // 對應下圖中 .parent("org.coderead")
}
return Collections.unmodifiableMap(this.packageInfo);
}
以上代碼可以對應 FastAutoGenerator 的客戶端代碼:
結論3:填寫多級包名時用.分隔
繼續閱讀 joinPackage 的源碼,才能搞清楚完整的包路徑:
/**
* 連接父子包名
*/
public String joinPackage(String subPackage) {
String parent = getParent();
// 如果 父包名 為空,則父子包名={用戶自定義的 子包名}
// 如果 父包名 不為空,則父子包名={父包名}.{子包名}
return StringUtils.isBlank(parent) ? subPackage : (parent + StringPool.DOT + subPackage);
}
/**
* 父包名
*/
public String getParent() {
// 如果 moduleName 不為空,則父包名={parent}.{moduleName}
if (StringUtils.isNotBlank(moduleName)) {
return parent + StringPool.DOT + moduleName;
}
// 如果 moduleName 為空,則父包名={parent}
return parent;
}
最后,包名中的.
會被替換成系統對應的文件分隔符。在 PathInfoHandler.putPathInfo(OutputFile outputFile, String module) 方法中用到的 PathInfoHandler.joinPath 方法如下:
圖中紅框中,就是將父子包名中的
.
替換成操作系統對應文件分隔符的代碼!
結論4:默認路徑=全局配置輸出路徑 + 父子包名
父子包名={parent}.{moduleName}.{具體某類輸出文件的子包名}
父子包名轉換為完整文件路徑時,會把
.
替換。