事故现场
我在项目开发中,由于项目业务需求出现多分支,各个分支的数据结构都是由初始业务演化而来。在开发环境中部署的是单实例MySQL,各分支采用数据库隔离,但数据表名仍相同,差异体现在数据列。当我在A分支的A数据库A表增加A字段后,再生成B分支的B数据库B表的代码。此时问题就出现了,生成的代码中包含A表的A字段,然而这并不是我想要的。
问题排查
mybatis-generator-core版本:1.4.0
代码生成的本质其实就是读取所连接数据库information_schema.columns表的数据,然后解析所属数据库|所属表|字段类型,然后进行处理后再生成代码。出现问题,百科了下没啥有用的方法(也可能是我百科手段比较简单),于是乎只有老老实实Debug。经过Debug跟踪发现其获取列在org.mybatis.generator.internal.db.DatabaseIntrospector#getColumns,代码如下:
private Map<ActualTableName, List<IntrospectedColumn>> getColumns(TableConfiguration tc) throws SQLException {
boolean delimitIdentifiers = tc.isDelimitIdentifiers() || StringUtility.stringContainsSpace(tc.getCatalog()) || StringUtility.stringContainsSpace(tc.getSchema()) || StringUtility.stringContainsSpace(tc.getTableName());
String localCatalog;
String localSchema;
String localTableName;
if (delimitIdentifiers) {
localCatalog = tc.getCatalog();
localSchema = tc.getSchema();
localTableName = tc.getTableName();
} else if (this.databaseMetaData.storesLowerCaseIdentifiers()) {
localCatalog = tc.getCatalog() == null ? null : tc.getCatalog().toLowerCase();
localSchema = tc.getSchema() == null ? null : tc.getSchema().toLowerCase();
localTableName = tc.getTableName() == null ? null : tc.getTableName().toLowerCase();
} else if (this.databaseMetaData.storesUpperCaseIdentifiers()) {
localCatalog = tc.getCatalog() == null ? null : tc.getCatalog().toUpperCase();
localSchema = tc.getSchema() == null ? null : tc.getSchema().toUpperCase();
localTableName = tc.getTableName() == null ? null : tc.getTableName().toUpperCase();
} else {
localCatalog = tc.getCatalog();
localSchema = tc.getSchema();
localTableName = tc.getTableName();
}
if (tc.isWildcardEscapingEnabled()) {
String escapeString = this.databaseMetaData.getSearchStringEscape();
StringBuilder sb = new StringBuilder();
StringTokenizer st;
String token;
if (localSchema != null) {
st = new StringTokenizer(localSchema, "_%", true);
while(true) {
if (!st.hasMoreTokens()) {
localSchema = sb.toString();
break;
}
token = st.nextToken();
if (token.equals("_") || token.equals("%")) {
sb.append(escapeString);
}
sb.append(token);
}
}
sb.setLength(0);
for(st = new StringTokenizer(localTableName, "_%", true); st.hasMoreTokens(); sb.append(token)) {
token = st.nextToken();
if (token.equals("_") || token.equals("%")) {
sb.append(escapeString);
}
}
localTableName = sb.toString();
}
Map<ActualTableName, List<IntrospectedColumn>> answer = new HashMap();
if (this.logger.isDebugEnabled()) {
String fullTableName = StringUtility.composeFullyQualifiedTableName(localCatalog, localSchema, localTableName, '.');
this.logger.debug(Messages.getString("Tracing.1", fullTableName));
}
// 这里就是Mybatis-Generator获取列的来源,由于我的项目特殊性,没有指定catalog和schema
ResultSet rs = this.databaseMetaData.getColumns(localCatalog, localSchema, localTableName, "%");
boolean supportsIsAutoIncrement = false;
boolean supportsIsGeneratedColumn = false;
ResultSetMetaData rsmd = rs.getMetaData();
int colCount = rsmd.getColumnCount();
for(int i = 1; i <= colCount; ++i) {
if ("IS_AUTOINCREMENT".equals(rsmd.getColumnName(i))) {
supportsIsAutoIncrement = true;
}
if ("IS_GENERATEDCOLUMN".equals(rsmd.getColumnName(i))) {
supportsIsGeneratedColumn = true;
}
}
while(rs.next()) {
IntrospectedColumn introspectedColumn = ObjectFactory.createIntrospectedColumn(this.context);
introspectedColumn.setTableAlias(tc.getAlias());
introspectedColumn.setJdbcType(rs.getInt("DATA_TYPE"));
introspectedColumn.setActualTypeName(rs.getString("TYPE_NAME"));
introspectedColumn.setLength(rs.getInt("COLUMN_SIZE"));
introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME"));
introspectedColumn.setNullable(rs.getInt("NULLABLE") == 1);
introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS"));
introspectedColumn.setRemarks(rs.getString("REMARKS"));
introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF"));
if (supportsIsAutoIncrement) {
introspectedColumn.setAutoIncrement("YES".equals(rs.getString("IS_AUTOINCREMENT")));
}
if (supportsIsGeneratedColumn) {
introspectedColumn.setGeneratedColumn("YES".equals(rs.getString("IS_GENERATEDCOLUMN")));
}
ActualTableName atn = new ActualTableName(rs.getString("TABLE_CAT"), rs.getString("TABLE_SCHEM"), rs.getString("TABLE_NAME"));
List<IntrospectedColumn> columns = (List)answer.get(atn);
if (columns == null) {
columns = new ArrayList();
answer.put(atn, columns);
}
((List)columns).add(introspectedColumn);
if (this.logger.isDebugEnabled()) {
this.logger.debug(Messages.getString("Tracing.2", introspectedColumn.getActualColumnName(), Integer.toString(introspectedColumn.getJdbcType()), atn.toString()));
}
}
this.closeResultSet(rs);
if (answer.size() > 1 && !StringUtility.stringContainsSQLWildcard(localSchema) && !StringUtility.stringContainsSQLWildcard(localTableName)) {
ActualTableName inputAtn = new ActualTableName(tc.getCatalog(), tc.getSchema(), tc.getTableName());
StringBuilder sb = new StringBuilder();
boolean comma = false;
ActualTableName atn;
for(Iterator var15 = answer.keySet().iterator(); var15.hasNext(); sb.append(atn.toString())) {
atn = (ActualTableName)var15.next();
if (comma) {
sb.append(',');
} else {
comma = true;
}
}
this.warnings.add(Messages.getString("Warning.25", inputAtn.toString(), sb.toString()));
}
return answer;
}
其中的this.databaseMetaData.getColumns最终是调用的mysql驱动包的com.mysql.cj.jdbc.DatabaseMetaDataUsingInfoSchema#getColumns里未获取到db导致的读取数据列定义错乱。
String db = this.getDatabase(catalog, schemaPattern);
db = this.pedantic ? db : StringUtils.unQuoteIdentifier(db, this.quotedId);
com.mysql.cj.jdbc.DatabaseMetaData#getDatabase代码如下:
protected String getDatabase(String catalog, String schema) {
if (this.databaseTerm.getValue() == DatabaseTerm.SCHEMA) {
return schema == null && (Boolean)this.nullDatabaseMeansCurrent.getValue() ? this.database : schema;
} else {
return catalog == null && (Boolean)this.nullDatabaseMeansCurrent.getValue() ? this.database : catalog;
}
}
关于的nullDatabaseMeansCurrent官网原文链接
其原文大致意思是:提供给DatabaseMetaData方法的 catalog 或 schema 参数值为null时,是否意味着使用当前数据库
解决方案
在jdbc连接url中增加参数nullDatabaseMeansCurrent=true指定使用当前数据库