Mybatis-mybatis自动生成代码提示"Cannot obtain primary key information from ..."解决方案

背景介绍

在使用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;
    }
  • 由于传入的catalogStrinformation_schema,导致生成的queryBufSHOW 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配置中还是有分别有catalogsechema两个配置项的.

通过单步调试发现

  • 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类)

参考

猜你喜欢

转载自blog.csdn.net/jpf254/article/details/79571396