如果寫一個(gè)和Mybatis-Plus類似的代碼生成框架,思路比較容易想到击困。核心的幾個(gè)步驟就是:
- 獲取數(shù)據(jù)庫表涎劈、字段信息广凸;
- 添加相應(yīng)的規(guī)則,補(bǔ)充額外的信心蛛枚。
- 根據(jù)表谅海、字段信息、新加的規(guī)則生成對(duì)應(yīng)的代碼蹦浦;
Mybatis-Plus整個(gè)框架依賴于Spring扭吁、Mybatis、模板引擎(freemaker或者velocity)和日志框架slf4j盲镶。
整個(gè)架構(gòu)如下:
如上所示:
- core:是整個(gè)框架的核心侥袜。包含了對(duì)數(shù)據(jù)庫實(shí)體的反射提取,分析數(shù)據(jù)表的字段的信息溉贿,數(shù)據(jù)的CRUD
- support:定義了相關(guān)的接口
- generate:賦值對(duì)相關(guān)代碼的生成系馆。
本篇只針對(duì)Generate部分進(jìn)行分析。
Generate (代碼生成)
其使用方式為首先創(chuàng)建一個(gè)AutoGenerator
對(duì)象顽照,此對(duì)象里面包含了所有的相關(guān)的配置信息由蘑,按照配置的不同類型組織為:
- 總配置ConfigBuilder,
會(huì)對(duì)下面的各個(gè)配置匯總代兵。
- 注入配置InjectionConfig
- 數(shù)據(jù)源配置DataSourceConfig
- 數(shù)據(jù)表配置StrategyConfig
- 包配置PackageConfig
- 模板配置TemplateConfig
- 全局配置GlobalConfig
使用
AutoGenerator
生成代碼的時(shí)候尼酿,外界設(shè)定好相關(guān)的配置類,然后賦值給AutoGenerator
相關(guān)屬性植影,最后調(diào)用execute
即可裳擎,非常簡(jiǎn)單。但是各種各樣的配置非常多思币,如果沒有深入的去了解鹿响,很有可能不能充分利用這個(gè)框架。
Config(配置詳解)
ConfigBuilder
ConfigBuilder
會(huì)對(duì)所有的配置再一次封裝谷饿,比如對(duì)某些為null的配置設(shè)定為默認(rèn)值惶我,過參數(shù)進(jìn)行過濾、驗(yàn)證等等博投。各個(gè)配置都有了之后绸贡,調(diào)用對(duì)應(yīng)的handler執(zhí)行處理。整個(gè)處理過程一定要注意配置初始化的順序毅哗,不能打亂听怕,比如最終的表生成策略依賴于模板配置、數(shù)據(jù)源配置等等虑绵。
具體來講對(duì)應(yīng)代碼如下:
public ConfigBuilder(PackageConfig packageConfig,
DataSourceConfig dataSourceConfig,
StrategyConfig strategyConfig,
TemplateConfig template,
GlobalConfig globalConfig) {
// 全局配置
if (null == globalConfig) {
this.globalConfig = new GlobalConfig();
} else {
this.globalConfig = globalConfig;
}
// 模板配置
if (null == template) {
this.template = new TemplateConfig();
} else {
this.template = template;
}
// 包配置
if (null == packageConfig) {
handlerPackage(this.template, this.globalConfig.getOutputDir(), new PackageConfig());
} else {
handlerPackage(this.template, this.globalConfig.getOutputDir(), packageConfig);
}
this.dataSourceConfig = dataSourceConfig;
handlerDataSource(dataSourceConfig);
// 策略配置
if (null == strategyConfig) {
this.strategyConfig = new StrategyConfig();
} else {
this.strategyConfig = strategyConfig;
}
handlerStrategy(this.strategyConfig);
}
這個(gè)類比較重要尿瞭,對(duì)于各個(gè)配置的handle也是在其進(jìn)行。比如獲取表屬性翅睛。
GlobalConfig
全局配置主要是對(duì)于整個(gè)自定生成環(huán)境的配置声搁。如目錄鸣峭,開發(fā)人員名稱,是否使用基類酥艳,文件命名等。具體來講包含有如下配置:
/**
* 生成文件的輸出目錄【默認(rèn) D 盤根目錄】
*/
private String outputDir = "D://";
/**
* 是否覆蓋已有文件
*/
private boolean fileOverride = false;
/**
* 是否打開輸出目錄
*/
private boolean open = true;
/**
* 是否在xml中添加二級(jí)緩存配置
*/
private boolean enableCache = true;
/**
* 開發(fā)人員
*/
private String author;
/**
* 開啟 Kotlin 模式
*/
private boolean kotlin = false;
/**
* 開啟 ActiveRecord 模式
*/
private boolean activeRecord = true;
/**
* 開啟 BaseResultMap
*/
private boolean baseResultMap = false;
/**
* 開啟 baseColumnList
*/
private boolean baseColumnList = false;
/**
* 各層文件名稱方式爬骤,例如: %Action 生成 UserAction
*/
private String mapperName;
private String xmlName;
private String serviceName;
private String serviceImplName;
private String controllerName;
/**
* 指定生成的主鍵的ID類型
*/
private IdType idType;
需要注意的幾個(gè)點(diǎn):
生成文件的輸出目錄因操作系統(tǒng)不同而不同充石。默認(rèn)是Windows的D盤
生成的主鍵的ID類型有多種。
AUTO(0, "數(shù)據(jù)庫ID自增"), INPUT(1, "用戶輸入ID"), ID_WORKER(2, "全局唯一ID"), UUID(3, "全局唯一ID"), NONE(4, "該類型為未設(shè)置主鍵類型"), ID_WORKER_STR(5, "字符串全局唯一ID");
PackageConfig
包相關(guān)的配置霞玄,這個(gè)配置比較簡(jiǎn)單骤铃。具體來講:
/**
* 父包名。如果為空坷剧,將下面子包名必須寫全部惰爬, 否則就只需寫子包名
*/
private String parent = "com.baomidou";
/**
* 父包模塊名。
*/
private String moduleName = null;
/**
* Entity包名
*/
private String entity = "entity";
/**
* Service包名
*/
private String service = "service";
/**
* Service Impl包名
*/
private String serviceImpl = "service.impl";
/**
* Mapper包名
*/
private String mapper = "mapper";
/**
* Mapper XML包名
*/
private String xml = "mapper.xml";
/**
* Controller包名
*/
private String controller = "web";
其實(shí)也就是對(duì)最終生成的目錄結(jié)構(gòu)的設(shè)定惫企。一個(gè)簡(jiǎn)單的例子:
TemplateConfig
模板配置類撕瞧,主要是對(duì)生成代碼文件格式的配置。我們生成不用層次的類對(duì)應(yīng)的模板是不同的狞尔,雖然可以通過字符串的方式來實(shí)現(xiàn)具體的自動(dòng)生成丛版。但是使用模板技術(shù)更加簡(jiǎn)單。對(duì)于每個(gè)層采用不同的模板:
private String entity = ConstVal.TEMPLATE_ENTITY_JAVA;
private String service = ConstVal.TEMPLATE_SERVICE;
private String serviceImpl = ConstVal.TEMPLATE_SERVICEIMPL;
private String mapper = ConstVal.TEMPLATE_MAPPER;
private String xml = ConstVal.TEMPLATE_XML;
private String controller = ConstVal.TEMPLATE_CONTROLLER;
這里的ConstVal
里面定義全局常量偏序。
以模板文件mapper.java.vm為例:
package ${package.Mapper};
import ${package.Entity}.${entity};
import ${superMapperClassPackage};
/**
* <p>
* $!{table.comment} Mapper 接口
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${kotlin})
interface ${table.mapperName} : ${superMapperClass}<${entity}>
#else
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {
}
#end
其對(duì)應(yīng)生成的代碼
package com.baomidou.test.mapper;
import com.baomidou.test.entity.Permission;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* <p>
* 權(quán)限表 Mapper 接口
* </p>
*
* @author Yanghu
* @since 2018-06-08
*/
public interface PermissionMapper extends BaseMapper<Permission> {
}
DataSourceConfig
數(shù)據(jù)庫配置主要就是定義好相關(guān)數(shù)據(jù)庫页畦,用戶名,密碼等研儒,便于連接到數(shù)據(jù)庫讀到想的表字段豫缨。具體來講包含對(duì)如下信息的配置:
/**
* 數(shù)據(jù)庫信息查詢
*/
private IDbQuery dbQuery;
/**
* 數(shù)據(jù)庫類型
*/
private DbType dbType;
/**
* PostgreSQL schemaname
*/
private String schemaname = "public";
/**
* 類型轉(zhuǎn)換
*/
private ITypeConvert typeConvert;
/**
* 驅(qū)動(dòng)連接的URL
*/
private String url;
/**
* 驅(qū)動(dòng)名稱
*/
private String driverName;
/**
* 數(shù)據(jù)庫連接用戶名
*/
private String username;
/**
* 數(shù)據(jù)庫連接密碼
*/
private String password;
需要說明一下IDbQuery、ITypeConvert端朵。
- IDbQuery是一個(gè)接口里面對(duì)查詢數(shù)據(jù)庫表好芭、字段、注釋信息的封裝冲呢。因?yàn)樾枰獫M足多種數(shù)據(jù)庫的自動(dòng)生成栓撞,所以需要正對(duì)不同數(shù)據(jù)庫實(shí)現(xiàn)IDbQuery不同的類。比如MySqlQuery就是對(duì)IDbQuery一種實(shí)現(xiàn)
@Override
public DbType dbType() {
return DbType.MYSQL;
}
@Override
public String tablesSql() {
return "show table status";
}
@Override
public String tableFieldsSql() {
return "show full fields from `%s`";
}
@Override
public String tableName() {
return "NAME";
}
@Override
public String tableComment() {
return "COMMENT";
}
@Override
public String fieldName() {
return "FIELD";
}
@Override
public String fieldType() {
return "TYPE";
}
@Override
public String fieldComment() {
return "COMMENT";
}
@Override
public String fieldKey() {
return "KEY";
}
@Override
public boolean isKeyIdentity(ResultSet results) throws SQLException {
return "auto_increment".equals(results.getString("Extra"));
}
- ITypeConvert接口是把數(shù)據(jù)庫中filed中的類型轉(zhuǎn)為java中對(duì)應(yīng)的數(shù)據(jù)類型碗硬。具體來講MySqlTypeConvert實(shí)現(xiàn)如下:
@Override
public DbColumnType processTypeConvert(String fieldType) {
String t = fieldType.toLowerCase();
if (t.contains("char") || t.contains("text")) {
return DbColumnType.STRING;
} else if (t.contains("bigint")) {
return DbColumnType.LONG;
} else if (t.contains("int")) {
return DbColumnType.INTEGER;
} else if (t.contains("date") || t.contains("time") || t.contains("year")) {
return DbColumnType.DATE;
} else if (t.contains("text")) {
return DbColumnType.STRING;
} else if (t.contains("bit")) {
return DbColumnType.BOOLEAN;
} else if (t.contains("decimal")) {
return DbColumnType.BIG_DECIMAL;
} else if (t.contains("clob")) {
return DbColumnType.CLOB;
} else if (t.contains("blob")) {
return DbColumnType.BLOB;
} else if (t.contains("binary")) {
return DbColumnType.BYTE_ARRAY;
} else if (t.contains("float")) {
return DbColumnType.FLOAT;
} else if (t.contains("double")) {
return DbColumnType.DOUBLE;
} else if (t.contains("json") || t.contains("enum")) {
return DbColumnType.STRING;
}
return DbColumnType.STRING;
}
注意這里為什么用contains
作為判斷瓤湘,因?yàn)閙ysql中可以設(shè)置數(shù)據(jù)類型具體大小。雖然大小不固定但是恩尾,其前綴是固定弛说。
- 其次還在DataSourceConfig中創(chuàng)建了數(shù)據(jù)庫連接對(duì)象提供給外界使用。
public Connection getConn() {
Connection conn = null;
try {
Class.forName(driverName);
conn = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
StrategyConfig
對(duì)具體生成的表字段進(jìn)行配置翰意。比如去掉哪些表前綴木人,字段前綴信柿;定義生成entity的公告字段及相關(guān)基類;排除對(duì)哪些表自動(dòng)生成醒第,或者對(duì)哪些表自動(dòng)生成渔嚷;是否根據(jù)表生成對(duì)應(yīng)的注釋。具體來講有如下設(shè)置:
/**
* 表名稠曼、字段名形病、是否使用下劃線命名(默認(rèn) false)
*/
public static boolean DB_COLUMN_UNDERLINE = false;
/**
* 是否大寫命名
*/
private boolean isCapitalMode = false;
/**
* 是否跳過視圖
*/
private boolean skipView = false;
/**
* 數(shù)據(jù)庫表映射到實(shí)體的命名策略
*/
private NamingStrategy naming = NamingStrategy.nochange;
/**
* 數(shù)據(jù)庫表字段映射到實(shí)體的命名策略<br/>
* 未指定按照 naming 執(zhí)行
*/
private NamingStrategy columnNaming = null;
/**
* 表前綴
*/
private String[] tablePrefix;
/**
* 字段前綴
*/
private String[] fieldPrefix;
/**
* 自定義繼承的Entity類全稱,帶包名
*/
private String superEntityClass;
/**
* 自定義基礎(chǔ)的Entity類霞幅,公共字段
*/
private String[] superEntityColumns;
/**
* 自定義繼承的Mapper類全稱漠吻,帶包名
*/
private String superMapperClass = ConstVal.SUPERD_MAPPER_CLASS;
/**
* 自定義繼承的Service類全稱,帶包名
*/
private String superServiceClass = ConstVal.SUPERD_SERVICE_CLASS;
/**
* 自定義繼承的ServiceImpl類全稱司恳,帶包名
*/
private String superServiceImplClass = ConstVal.SUPERD_SERVICEIMPL_CLASS;
/**
* 自定義繼承的Controller類全稱途乃,帶包名
*/
private String superControllerClass;
/**
* 需要包含的表名(與exclude二選一配置)
*/
private String[] include = null;
/**
* 需要排除的表名
*/
private String[] exclude = null;
/**
* 【實(shí)體】是否生成字段常量(默認(rèn) false)<br>
* -----------------------------------<br>
* public static final String ID = "test_id";
*/
private boolean entityColumnConstant = false;
/**
* 【實(shí)體】是否為構(gòu)建者模型(默認(rèn) false)<br>
* -----------------------------------<br>
* public User setName(String name) { this.name = name; return this; }
*/
private boolean entityBuilderModel = false;
/**
* 【實(shí)體】是否為lombok模型(默認(rèn) false)<br>
* <a >document</a>
*/
private boolean entityLombokModel = false;
/**
* Boolean類型字段是否移除is前綴(默認(rèn) false)<br>
* 比如 : 數(shù)據(jù)庫字段名稱 : 'is_xxx',類型為 : tinyint. 在映射實(shí)體的時(shí)候則會(huì)去掉is,在實(shí)體類中映射最終結(jié)果為 xxx
*/
private boolean entityBooleanColumnRemoveIsPrefix = false;
/**
* 生成 <code>@RestController</code> 控制器
* <pre>
* <code>@Controller</code> -> <code>@RestController</code>
* </pre>
*/
private boolean restControllerStyle = false;
/**
* 駝峰轉(zhuǎn)連字符
* <pre>
* <code>@RequestMapping("/managerUserActionHistory")</code> -> <code>@RequestMapping("/manager-user-action-history")</code>
* </pre>
*/
private boolean controllerMappingHyphenStyle = false;
/**
* 是否生成實(shí)體時(shí),生成字段注解
*/
private boolean entityTableFieldAnnotationEnable = false;
/**
* 樂觀鎖屬性名稱
*/
private String versionFieldName;
/**
* 邏輯刪除屬性名稱
*/
private String logicDeleteFieldName;
/**
* 表填充字段
*/
private List<TableFill> tableFillList = null;
小結(jié)
相關(guān)配置介紹完了扔傅,可以看到每一個(gè)配置對(duì)應(yīng)一個(gè)具體的層面耍共。這樣的好處在于職責(zé)清晰。從代碼層面上講也使用到了諸如觀面模式猎塞,策略模式等划提。
Handle(處理配置)
各個(gè)配置都設(shè)置好了之后就開始進(jìn)行處理了。其實(shí)就調(diào)用了一個(gè)方法而已
public void execute() {
logger.debug("==========================準(zhǔn)備生成文件...==========================");
if(null == this.config) {
this.config = new ConfigBuilder(this.packageInfo, this.dataSource, this.strategy, this.template, this.globalConfig);
if(null != this.injectionConfig) {
this.injectionConfig.setConfig(this.config);
}
}
if(null == this.templateEngine) {
this.templateEngine = new VelocityTemplateEngine();
}
this.templateEngine.init(this.pretreatmentConfigBuilder(this.config)).mkdirs().batchOutput().open();
logger.debug("==========================文件生成完成P舷怼E敉!==========================");
}
上面代理主要分為兩步骇塘。
- 第一步:根據(jù)配置信息伊履,實(shí)例化一個(gè)ConfigBuilder。它屏蔽了對(duì)配置如何處理的細(xì)節(jié)款违,初始化完成之后唐瀑,ConfigBuilder就完成了對(duì)所有配置的加載,以及對(duì)應(yīng)數(shù)據(jù)庫表插爹、字段的提取哄辣。
- 第二步:調(diào)用模板引擎,傳入ConfigBuilder赠尾。模板引擎根據(jù)ConfigBuilder填充對(duì)應(yīng)的模板力穗。最終生成代碼。
構(gòu)造ConfigBuilder
在構(gòu)造ConfigBuilder中气嫁,會(huì)一一相關(guān)的配置進(jìn)行handle当窗。這里主要講一下handlerStrategy
,因?yàn)檫@個(gè)方法包含了對(duì)數(shù)據(jù)庫信息的提取過程寸宵,并且將數(shù)據(jù)庫表信息映射為實(shí)體崖面。
最終會(huì)走到一個(gè)名叫getTablesInfo
方法元咙。里面涉及到兩個(gè)基本的、對(duì)數(shù)據(jù)表抽象的實(shí)體TableField
與TableInfo
基礎(chǔ)實(shí)體
TableField
TableField
的內(nèi)容:
/**
* 是否需要進(jìn)行轉(zhuǎn)換
*/
private boolean convert;
/**
* 是否為主鍵
*/
private boolean keyFlag;
/**
* 主鍵是否為自增類型
*/
private boolean keyIdentityFlag;
/**
* 對(duì)應(yīng)數(shù)據(jù)表的名稱
*/
private String name;
/**
* 轉(zhuǎn)換之后的類型
*/
private String type;
/**
* 轉(zhuǎn)換之后的屬性名
*/
private String propertyName;
/**
* 對(duì)應(yīng)數(shù)據(jù)表的類型
*/
private DbColumnType columnType;
/**
* 該字段的注釋信息
*/
private String comment;
/**
* 填充信息
*/
private String fill;
/**
* 自定義查詢字段列表
*/
private Map<String, Object> customMap;
注意在設(shè)置setConvert的時(shí)候是傳入的一個(gè)StrategyConfig巫员,根據(jù)StrategyConfig的某些配置確定是否需要轉(zhuǎn)換庶香。
TableInfo
TableInfo是對(duì)數(shù)據(jù)表的抽象,具體來講:
/**
* 是否轉(zhuǎn)換
*/
private boolean convert;
/**
* 表名
*/
private String name;
/**
* 表注釋
*/
private String comment;
/**
* 表所對(duì)應(yīng)的實(shí)體名
*/
private String entityName;
/**
* 表所對(duì)應(yīng)的mapper名
*/
private String mapperName;
/**
* 表所對(duì)應(yīng)的xml名
*/
private String xmlName;
/**
* 表所對(duì)應(yīng)的service名
*/
private String serviceName;
/**
* 表所對(duì)應(yīng)的serviceimpl名
*/
private String serviceImplName;
/**
* 表所對(duì)應(yīng)的controller名
*/
private String controllerName;
/**
* 表所包含的所有field集合
*/
private List<TableField> fields;
/**
* 公共字段
*/
private List<TableField> commonFields;
/**
* 所依賴的包名
*/
private List<String> importPackages = new ArrayList<>();
/**
* 說有字段連在一起的字符串简识,用于日志信息
*/
private String fieldNames;
這需要注意一點(diǎn)的就是在設(shè)置fields的時(shí)候需要根據(jù)fields的數(shù)據(jù)類型引入相應(yīng)的包名赶掖。做法就是在設(shè)置fields的時(shí)候根據(jù)field類型引入。具體來講:
public void setFields(List<TableField> fields) {
if (CollectionUtils.isNotEmpty(fields)) {
this.fields = fields;
// 收集導(dǎo)入包信息财异,注意為什么用HashSet。因?yàn)镠ashSetk可以自動(dòng)去除重復(fù)的key
Set<String> pkgSet = new HashSet<>();
for (TableField field : fields) {
if (null != field.getColumnType() && null != field.getColumnType().getPkg()) {
pkgSet.add(field.getColumnType().getPkg());
}
if (field.isKeyFlag()) {
// 主鍵
if (field.isConvert() || field.isKeyIdentityFlag()) {
pkgSet.add("com.baomidou.mybatisplus.annotations.TableId");
}
// 自增
if (field.isKeyIdentityFlag()) {
pkgSet.add("com.baomidou.mybatisplus.enums.IdType");
}
} else if (field.isConvert()) {
// 普通字段
pkgSet.add("com.baomidou.mybatisplus.annotations.TableField");
}
if (null != field.getFill()) {
// 填充字段
pkgSet.add("com.baomidou.mybatisplus.annotations.TableField");
pkgSet.add("com.baomidou.mybatisplus.enums.FieldFill");
}
}
if (!pkgSet.isEmpty()) {
this.importPackages = new ArrayList<>(Arrays.asList(pkgSet.toArray(new String[]{})));
}
}
}
getTablesInfo
先介紹一下其中的處理邏輯:
判斷是否設(shè)置了同時(shí)設(shè)置了include和exclude
保存所有表的信息唱遭,包含排除的表戳寸,需要生成的表。
-
根據(jù)sql查詢表的信息拷泽,然后依次將數(shù)據(jù)映射到對(duì)應(yīng)的基礎(chǔ)實(shí)體上(TableInfo疫鹊,F(xiàn)iledInfo)。
- 如果設(shè)置了include或者exclude司致,再進(jìn)一步刪選拆吆。比如過濾用戶輸入不存在的表。
- 最終得到includeTableList表脂矫,禮包包含了需要轉(zhuǎn)換的表名稱
- 調(diào)用
convertTableFields
將表中的filed轉(zhuǎn)為基礎(chǔ)實(shí)體枣耀。 - 最后調(diào)用
processTable
將表、字段信息庭再、其他信息填充到TableInfo中捞奕。完成TableInfo基礎(chǔ)實(shí)體的構(gòu)造。
其中用到的幾條sql語句如下:
-
show table status
:獲取表信息拄轻,比如表名颅围、創(chuàng)建(修改)時(shí)間、表注釋恨搓,數(shù)量條數(shù)等
-
show full fields from xxx
:從特定表中獲取表所有字段的信息院促,比如字段名、字段類型斧抱,字段注釋以及該表的主鍵等常拓。
其中使用的而是JDBC最為簡(jiǎn)單的讀取數(shù)據(jù)庫。代碼簡(jiǎn)化一下
//映射Tables
preparedStatement = connection.prepareStatement(tablesSql);
ResultSet results = preparedStatement.executeQuery();
TableInfo tableInfo;
while (results.next()) {
......
includeTableList.add(tableInfo);
}
//映射Fields
for (TableInfo ti : includeTableList) {
PreparedStatement preparedStatement = connection.prepareStatement(tableFieldsSql);
ResultSet results = preparedStatement.executeQuery();
TableField field = new TableField();
while (results.next()) {
......
fieldList.add(field);
}
}
tableInfo.setFields(fieldList);
tableInfo.setCommonFields(commonFieldList);
}
至此所有表以及所有表對(duì)應(yīng)的字段已經(jīng)完全映射到了基礎(chǔ)實(shí)體辉浦。接下來就是根據(jù)基礎(chǔ)實(shí)體的內(nèi)容墩邀,填充對(duì)應(yīng)的模板。
Template(模板生成)
有了上面所產(chǎn)生的實(shí)體盏浙,下面就是填模板的過程了眉睹。入口如下:
if(null == this.templateEngine) {
this.templateEngine = new VelocityTemplateEngine();
}
this.templateEngine.init(this.pretreatmentConfigBuilder(this.config)).mkdirs().batchOutput().open();
TemplateEngine(模板引擎)
MP現(xiàn)在支持兩種模板引擎Velocity荔茬、Freemarker。這里以Velocity為例竹海。
模板生成相關(guān)一共有三個(gè)類慕蔚,分別是AbstractTemplateEngine、FreemarkerTemplateEngine斋配、VelocityTemplateEngine孔飒。AbstractTemplateEngine是抽象類定義了相關(guān)的接口。具體來講艰争,提供了如下信息:
其中的writer
與templateFilePath
為抽象方法坏瞄,根據(jù)不同的模板引擎,選擇不同的實(shí)現(xiàn)甩卓。最終是調(diào)用batchOutput來輸出所有自動(dòng)生成的代碼鸠匀。
在batchOutPut中將各個(gè)層級(jí)的對(duì)象,根據(jù)模板路徑逾柿,生成最終的文件缀棍。
//遍歷所有的表信息,生成文件
List<TableInfo> tableInfoList = this.getConfigBuilder().getTableInfoList();
for (TableInfo tableInfo : tableInfoList) {
Map<String, Object> objectMap = this.getObjectMap(tableInfo);
Map<String, String> pathInfo = this.getConfigBuilder().getPathInfo();
TemplateConfig template = this.getConfigBuilder().getTemplate();
// Mp.java
String entityName = tableInfo.getEntityName();
if (null != entityName && null != pathInfo.get(ConstVal.ENTITY_PATH)) {
String entityFile = String.format((pathInfo.get(ConstVal.ENTITY_PATH) + File.separator + "%s" + this.suffixJavaOrKt()), entityName);
if (this.isCreate(entityFile)) {
this.writer(objectMap, this.templateFilePath(template.getEntity(this.getConfigBuilder().getGlobalConfig().isKotlin())), entityFile);
}
}
......
// MpMapper.xml
// IMpService.java
// MpServiceImpl.java
// MpController.java
}
}
}
接下來看一下VelocityTemplateEngine中的write方法机错。首先會(huì)進(jìn)行初始化配置
public VelocityTemplateEngine init(ConfigBuilder configBuilder) {
//將configBuilder傳給父類爬范,在父類中需要用到
super.init(configBuilder);
if (null == velocityEngine) {
Properties p = new Properties();
p.setProperty(ConstVal.VM_LOADPATH_KEY, ConstVal.VM_LOADPATH_VALUE);
p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, "");
p.setProperty(Velocity.ENCODING_DEFAULT, ConstVal.UTF8);
p.setProperty(Velocity.INPUT_ENCODING, ConstVal.UTF8);
p.setProperty("file.resource.loader.unicode", "true");
//初始化模板引擎
velocityEngine = new VelocityEngine(p);
}
return this;
}
父類中調(diào)用writer,并將objectMap(包含所有的映射信息)傳入,根據(jù)templatePath(不同類型模板不一樣)創(chuàng)建template弱匪。最后將模板內(nèi)容依據(jù)objectMap替換掉青瀑。其中的模板路徑則根據(jù)之前的TemplateConfig得到
@Override
public void writer(Map<String, Object> objectMap, String templatePath, String outputFile) throws Exception {
if (StringUtils.isEmpty(templatePath)) {
return;
}
Template template = velocityEngine.getTemplate(templatePath, ConstVal.UTF8);
FileOutputStream fos = new FileOutputStream(outputFile);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, ConstVal.UTF8));
template.merge(new VelocityContext(objectMap), writer);
writer.close();
logger.debug("模板:" + templatePath + "; 文件:" + outputFile);
}
這里以生存entity為例。
生成的entity
package com.baomidou.test.entity;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;
/**
* <p>
* 權(quán)限表
* </p>
*
* @author wesly
* @since 2018-06-08
*/
public class Permission extends Model<Permission> {
private static final long serialVersionUID = 1L;
/**
* 主鍵
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 上級(jí)ID
*/
private Long pid;
......
private String description;
@Override
public String toString() {
return "Permission{" +
", id=" + id +
", pid=" + pid +
", title=" + title +
", type=" + type +
", state=" + state +
", sort=" + sort +
", url=" + url +
", permCode=" + permCode +
", icon=" + icon +
", description=" + description +
"}";
}
}
總結(jié)
Generate部分總體來講思路比價(jià)簡(jiǎn)單萧诫。麻煩的部分在于如何去對(duì)各個(gè)部拆分狱窘。最后簡(jiǎn)單畫出了怎個(gè)生成框架的類圖結(jié)構(gòu)如下: