背景介绍
在使用Mybatis自动生成代码功能时,出现提示Cannot obtain primary key information from the database, generated objects may be incomplete
,导致Mapper下只有insert()
和insertSelective()
,其余update()
,delete()
,select()
方法都没有生成自动生成,使用的Mybatis版本为3.4.6
,mysql-connector-java版本为8.0.9-rc
.(解决方案在总结中,可直接使用)
分析
mybatis generator自动创建代码及相关问题中使用将mysql-connector-java降低版本的方式解决.但是由于希望继续使用高版本,因此继续寻找其余解决方案.
为何无法生成主键信息?
单步调试找到出现错误的地方在DataBaseMetaData.getPrimaryKeys()
中.
public java.sql.ResultSet getPrimaryKeys(String catalog, String schema, final String table) throws SQLException {
...
try {
new IterateBlock<String>(getCatalogIterator(catalog)) {
@Override
void forEach(String catalogStr) throws SQLException {
ResultSet rs = null;
try {
StringBuilder queryBuf = new StringBuilder("SHOW KEYS FROM ");
queryBuf.append(StringUtils.quoteIdentifier(table, DatabaseMetaData.this.quotedId, DatabaseMetaData.this.pedantic));
queryBuf.append(" FROM ");
queryBuf.append(StringUtils.quoteIdentifier(catalogStr, DatabaseMetaData.this.quotedId, DatabaseMetaData.this.pedantic));
//此时queryBuf是 SHOW KEYS FROM `user` FROM `information_schema`
rs = stmt.executeQuery(queryBuf.toString());
...
}.doForAll();
}
...
return results;
}
- 由于传入的
catalogStr
是information_schema
,导致生成的queryBuf
是SHOW KEYS FROM user FROM information_schema
- 而在
information_schema
中无user
数据表,查询此语句就会抛出异常,导致无法获取user
主键信息.
那么catalogStr
从何而来呢?
catalogStr
又是因为getPrimaryKeys()
中传入的参数catalog
为null,导致getCatalogIterator()
会获取MySQL中所有数据库,按字母顺序排序,而information_schema
一般又是第一个数据库
protected IteratorWithCleanup<String> getCatalogIterator(String catalogSpec) throws SQLException {
IteratorWithCleanup<String> allCatalogsIter;
if (catalogSpec != null) {
allCatalogsIter = new SingleStringIterator(this.pedantic ? catalogSpec : StringUtils.unQuoteIdentifier(catalogSpec, this.quotedId));
} else if (this.nullCatalogMeansCurrent) {
//"nullCatalogMeansCurrent"非常重要,是下文解决方案的关键
allCatalogsIter = new SingleStringIterator(this.database);
} else {
//进入此分支,生成包含MySQL中所有数据库的IteratorWithCleanup
allCatalogsIter = new ResultSetIterator(getCatalogs(), 1);
}
return allCatalogsIter;
}
catalog从何而来?
从上一小节可以得知catalog
好像和数据库,即schema
有点相似,那他们之间又有什么关系呢?
是因为MySQL不支持catalog
导致mysql-connector-java直接使用sechema
代替catalog
了,但是由于Mybatis要兼容众多数据库,所以在Mybatis配置中还是有分别有catalog
和sechema
两个配置项的.
通过单步调试发现
catalog
是调用FullyQualifiedTable.getCatalog()
获得的- 而
FullyQualifiedTable.getCatalog()
是根据TableConfiguration.getCatalog()
获得 TableConfiguration
就是xml中<table>
配置Java类对应的Java类,它们是一一对应的
所以是否只要在<table>
中配置catalog
就解决问题了呢?
配置catalog后
在<table>
中配置catalog
后再生成一次代码,便不再提示Cannot obtain primary key...
,Mapper也生成了其余方法,但是却多了一个文件夹,这个文件夹pocketpiano_spring
便是数据库名,也是catalog
的配置值.
其实这样也算是解决了问题,只是并非尽善尽美,至于为什么生成pocketpiano_spring
文件夹便不再分析,因为找到了另外一种完美的解决方案.
最终解决方案
由上文可知,mysql不支持catalog,以Mybatis的智能性,如果告知Mybatis当前使用的数据库不支持catalog
,Mybatis能否正常工作?我们又如何告知Mybatis呢?
答案就是nullCatalogMeansCurrent.在getCatalogIterator()
可以发现nullCatalogMeansCurrent
会影响IteratorWithCleanup
的生成结果,如果nullCatalogMeansCurrent
为true,则IteratorWithCleanup
只包含this.database
,并不包含所有schema
.(往上翻,上面有代码)
nullCatalogMeansCurrent
是jdbc url的一个参数,只不过看不太懂中文的解释.
查看官方文档,解释如下:
- 当
DatabaseMetaData
调用其方法获取catalog
参数时,若nullCatalogMeansCurrent
为true,那么catalog
为null就代表使用当前目录(当前目录即当前数据库).这并不兼容jdbc,而是遵循早期版本的驱动程序的遗留行为.
因此也不需要设置<table>
的catalog
,只需在jdbc url后增加 ?nullCatalogMeansCurrent=true
即可(但此时不会生成withBLOBs
类)
总结
- 在jdbc url后增加
?nullCatalogMeansCurrent=true
即可(但此时不会生成withBLOBs
类)