源码分析
Mybatis-plus逆向工程生成源码的主要流程是在AutoGenerator调用execute()方法时,会根据你设置的DataSourceConfig里的
DbType属性,判断你的数据库类型,然后从你的数据库查询出来相应的数据表名,而后将信息存入到TableInfo里,而后进行逆向生
成,进行逆向生成的过程中。
第一步解析配置:
/**
* 生成代码
*/
public void execute() {
logger.debug("==========================准备生成文件...==========================");
// 初始化配置
if (null == config) {
//这里会生成配置,非常重要,也是唯一受可以受我们影响的,也是最容易出问题的,我就是在这里出了错。
config = new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig);
if (null != injectionConfig) {
injectionConfig.setConfig(config);
}
}
if (null == templateEngine) {
// 为了兼容之前逻辑,采用 Velocity 引擎 【 默认 】
templateEngine = new VelocityTemplateEngine();
}
// 模板引擎初始化执行文件输出
templateEngine.init(this.pretreatmentConfigBuilder(config)).mkdirs().batchOutput().open();
logger.debug("==========================文件生成完成!!!==========================");
}
第二步:使用new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig);去生成config配置信息。
//这里是是创建一个ConfigBuilder
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;
//这一步,就是解析你配置的dataSourceConfig对象,这一步代码会产生三个非常重要的属性
handlerDataSource(dataSourceConfig);
// 策略配置
if (null == strategyConfig) {
this.strategyConfig = new StrategyConfig();
} else {
this.strategyConfig = strategyConfig;
}
//SQLITE 数据库不支持注释获取
commentSupported = !dataSourceConfig.getDbType().equals(DbType.SQLITE);
//这里会进行非常重要的功能解析,其中要针对哪些表的操作,就是发生在这里
handlerStrategy(this.strategyConfig);
}
/**
* 处理数据源配置,这是非常非常关键的一步,这三个属性接下来会用到
*
* @param config DataSourceConfig
*/
private void handlerDataSource(DataSourceConfig config) {
connection = config.getConn();
dbType = config.getDbType();
dbQuery = config.getDbQuery();
}
第三步:执行handlerStrategy(this.strategyConfig);这里会针对你的配置,来对表进行处理
private void handlerStrategy(StrategyConfig config) {
processTypes(config);
//这一步就是根据你配置的信息,去获取相关的表及表的column信息
tableInfoList = getTablesInfo(config);
}
第四步:执行 getTablesInfo(config);
private List<TableInfo> getTablesInfo(StrategyConfig config) {
boolean isInclude = (null != config.getInclude() && config.getInclude().length > 0);
boolean isExclude = (null != config.getExclude() && config.getExclude().length > 0);
if (isInclude && isExclude) {
throw new RuntimeException("<strategy> 标签中 <include> 与 <exclude> 只能配置一项!");
}
if (config.getNotLikeTable() != null && config.getLikeTable() != null) {
throw new RuntimeException("<strategy> 标签中 <likeTable> 与 <notLikeTable> 只能配置一项!");
}
//TableInfo里封装的就是各个表和表里的列对应的信息集合,这个集合深刻影响着我们生成的entity
List<TableInfo> tableList = new ArrayList<>();
//需要反向生成或排除的表信息
List<TableInfo> includeTableList = new ArrayList<>();
List<TableInfo> excludeTableList = new ArrayList<>();
//不存在的表名
Set<String> notExistTables = new HashSet<>();
//这里非常重要,这里会判断你要解析的数据库类型,并且生成对数据库的数据表查询语句
try {
//这里获取tableSql,tableSql就是一条查询数据库信息的sql语句,不同的数据库类型,拥有不同的sql,我们可以看不同数据库实现的IDbQuery
//这里使用的dbQuery就是第二步解析datasource获取的dbQuery,而这个属性,在创建datasourceconfig时,我们可以指定
String tablesSql = dbQuery.tablesSql();
//这里的dbType也是第二步解析的datasource获取的
if (DbType.POSTGRE_SQL == this.dbType) {
String schema = dataSourceConfig.getSchemaName();
if (schema == null) {
//pg 默认 schema=public
schema = "public";
dataSourceConfig.setSchemaName(schema);
}
tablesSql = String.format(tablesSql, schema);
} else if (DbType.KINGBASE_ES == this.dbType) {
String schema = dataSourceConfig.getSchemaName();
if (schema == null) {
//kingbase 默认 schema=PUBLIC
schema = "PUBLIC";
dataSourceConfig.setSchemaName(schema);
}
tablesSql = String.format(tablesSql, schema);
} else if (DbType.DB2 == this.dbType) {
String schema = dataSourceConfig.getSchemaName();
if (schema == null) {
//db2 默认 schema=current schema
schema = "current schema";
dataSourceConfig.setSchemaName(schema);
}
tablesSql = String.format(tablesSql, schema);
}
//oracle数据库表太多,出现最大游标错误
else if (DbType.ORACLE == this.dbType) {
//我们设置的this.dbType为oracle,因此会走这里,我们可以看到这里首先获取了datasourceConfig的SchemaName属性
String schema = dataSourceConfig.getSchemaName();
//这里我们可以看到,如果我们不手动在datasourceConfig里设置scheam的话,那么它会使用username作为默认的shceam。
if (schema == null) {
schema = dataSourceConfig.getUsername().toUpperCase();
dataSourceConfig.setSchemaName(schema);
}
//这里就是生成数据库查询表的语句,我的正是这里出了问题,因为我的数据库用户名和密码,并不是和我数据库的
scheam相同,所以导致,生成的查询表语句,查不到对应的表,而官方示例的代码里又没有配置scheam的,导致我的
查不到对应表。而且基本上排查到这一步,就能查询出问题了。
tablesSql = String.format(tablesSql, schema);
}
StringBuilder sql = new StringBuilder(tablesSql);
if (config.isEnableSqlFilter()) {
if (config.getLikeTable() != null) {
sql.append(" AND ").append(dbQuery.tableName()).append(" LIKE '").append(config.getLikeTable().getValue()).append("'");
} else if (config.getNotLikeTable() != null) {
sql.append(" AND ").append(dbQuery.tableName()).append(" NOT LIKE '").append(config.getNotLikeTable().getValue()).append("'");
}
if (isInclude) {
sql.append(" AND ").append(dbQuery.tableName()).append(" IN (")
.append(Arrays.stream(config.getInclude()).map(tb -> "'" + tb + "'").collect(Collectors.joining(","))).append(")");
} else if (isExclude) {
sql.append(" AND ").append(dbQuery.tableName()).append(" NOT IN (")
.append(Arrays.stream(config.getExclude()).map(tb -> "'" + tb + "'").collect(Collectors.joining(","))).append(")");
}
}
TableInfo tableInfo;
//这一步同样是使用的第二步解析datasource获取的connection,我的bug在上一步已经解决,加入该步骤是为了说明代码生成器的拓展点,
try (PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
ResultSet results = preparedStatement.executeQuery()) {
while (results.next()) {
String tableName = results.getString(dbQuery.tableName());
if (StringUtils.isNotBlank(tableName)) {
tableInfo = new TableInfo();
tableInfo.setName(tableName);
if (commentSupported) {
String tableComment = results.getString(dbQuery.tableComment());
if (config.isSkipView() && "VIEW".equals(tableComment)) {
// 跳过视图
continue;
}
tableInfo.setComment(tableComment);
}
if (isInclude) {
for (String includeTable : config.getInclude()) {
// 忽略大小写等于 或 正则 true
if (tableNameMatches(includeTable, tableName)) {
includeTableList.add(tableInfo);
} else {
//过滤正则表名
if (!REGX.matcher(includeTable).find()) {
notExistTables.add(includeTable);
}
}
}
} else if (isExclude) {
for (String excludeTable : config.getExclude()) {
// 忽略大小写等于 或 正则 true
if (tableNameMatches(excludeTable, tableName)) {
excludeTableList.add(tableInfo);
} else {
//过滤正则表名
if (!REGX.matcher(excludeTable).find()) {
notExistTables.add(excludeTable);
}
}
}
}
tableList.add(tableInfo);
} else {
System.err.println("当前数据库为空!!!");
}
}
}
// 将已经存在的表移除,获取配置中数据库不存在的表
for (TableInfo tabInfo : tableList) {
notExistTables.remove(tabInfo.getName());
}
if (notExistTables.size() > 0) {
System.err.println("表 " + notExistTables + " 在数据库中不存在!!!");
}
// 需要反向生成的表信息
if (isExclude) {
tableList.removeAll(excludeTableList);
includeTableList = tableList;
}
if (!isInclude && !isExclude) {
includeTableList = tableList;
}
// 性能优化,只处理需执行表字段 github issues/219
includeTableList.forEach(ti -> convertTableFields(ti, config));
} catch (SQLException e) {
e.printStackTrace();
}
return processTable(includeTableList, config.getNaming(), config);
}
拓展总结:从上面的源码分析我们可以看出来,自动代码生成器提供的一个拓展点,就是DatasourceConfig,他里面的IDbQuery类型的属性,影响着entity的类名、属性名称等,IDbQuery是一个接口,不同的数据库类型,对应者不同的IDbQuery,当然我们可以自定义一个IDbQuery来影响我们的Entity生成。